metaruby 1.0.0 → 2.0.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 +4 -4
 - data/.travis.yml +6 -3
 - data/Gemfile +6 -0
 - data/Rakefile +12 -2
 - data/lib/metaruby.rb +2 -0
 - data/lib/metaruby/attributes.rb +52 -60
 - data/lib/metaruby/backward/singleton_class_p.rb +14 -0
 - data/lib/metaruby/dsls.rb +31 -1
 - data/lib/metaruby/dsls/doc.rb +33 -25
 - data/lib/metaruby/dsls/find_through_method_missing.rb +78 -16
 - data/lib/metaruby/gui.rb +1 -0
 - data/lib/metaruby/gui/html/exception_view.css +4 -4
 - data/lib/metaruby/gui/html/page_body.rhtml +0 -3
 - data/lib/metaruby/gui/model_browser.rb +35 -20
 - data/lib/metaruby/gui/model_hierarchy.rb +267 -0
 - data/lib/metaruby/gui/model_selector.rb +63 -40
 - data/lib/metaruby/model_as_class.rb +9 -13
 - data/lib/metaruby/model_as_module.rb +2 -5
 - data/lib/metaruby/registration.rb +25 -19
 - data/lib/metaruby/test.rb +3 -0
 - data/lib/metaruby/version.rb +1 -1
 - metadata +5 -4
 
| 
         @@ -1,5 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'metaruby/attributes'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module MetaRuby
         
     | 
| 
       2 
4 
     | 
    
         
             
                module DSLs
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # Common definition of #respond_to_missing? and #method_missing to be
         
     | 
| 
      
 6 
     | 
    
         
            +
                    # used in conjunction with {DSLs.find_through_method_missing} and
         
     | 
| 
      
 7 
     | 
    
         
            +
                    # {DSLs.has_through_method_missing?}
         
     | 
| 
      
 8 
     | 
    
         
            +
                    #
         
     | 
| 
      
 9 
     | 
    
         
            +
                    # @example resolve 'event' objects using method_missing
         
     | 
| 
      
 10 
     | 
    
         
            +
                    #   class Task
         
     | 
| 
      
 11 
     | 
    
         
            +
                    #      # Tests if this task has an event by this name
         
     | 
| 
      
 12 
     | 
    
         
            +
                    #      #
         
     | 
| 
      
 13 
     | 
    
         
            +
                    #      # @param [String] name
         
     | 
| 
      
 14 
     | 
    
         
            +
                    #      # @return [Boolean]
         
     | 
| 
      
 15 
     | 
    
         
            +
                    #      def has_event?(name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    #      end
         
     | 
| 
      
 17 
     | 
    
         
            +
                    #
         
     | 
| 
      
 18 
     | 
    
         
            +
                    #      # Finds an event by name
         
     | 
| 
      
 19 
     | 
    
         
            +
                    #      #
         
     | 
| 
      
 20 
     | 
    
         
            +
                    #      # @param [String] name
         
     | 
| 
      
 21 
     | 
    
         
            +
                    #      # @return [Object,nil] the found event, or nil if there is no
         
     | 
| 
      
 22 
     | 
    
         
            +
                    #      #   event by this name
         
     | 
| 
      
 23 
     | 
    
         
            +
                    #      def find_event(name)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    #      end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    #
         
     | 
| 
      
 26 
     | 
    
         
            +
                    #      include MetaRuby::DSLs::FindThroughMethodMissing
         
     | 
| 
      
 27 
     | 
    
         
            +
                    #
         
     | 
| 
      
 28 
     | 
    
         
            +
                    #      # Check if the given method matches a find object
         
     | 
| 
      
 29 
     | 
    
         
            +
                    #      def has_through_method_missing?(m)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    #        MetaRuby::DSLs.has_through_method_missing?(
         
     | 
| 
      
 31 
     | 
    
         
            +
                    #           self, m, '_event' => :has_event?) || super
         
     | 
| 
      
 32 
     | 
    
         
            +
                    #      end
         
     | 
| 
      
 33 
     | 
    
         
            +
                    #
         
     | 
| 
      
 34 
     | 
    
         
            +
                    #      # Check if the given method matches a find object
         
     | 
| 
      
 35 
     | 
    
         
            +
                    #      def find_through_method_missing(m, args)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    #        MetaRuby::DSLs.find_through_method_missing(
         
     | 
| 
      
 37 
     | 
    
         
            +
                    #           self, m, args, '_event' => :find_event) || super
         
     | 
| 
      
 38 
     | 
    
         
            +
                    #      end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    #
         
     | 
| 
      
 41 
     | 
    
         
            +
                    module FindThroughMethodMissing
         
     | 
| 
      
 42 
     | 
    
         
            +
                        # Empty implementation of has_through_method_missing? to allow for
         
     | 
| 
      
 43 
     | 
    
         
            +
                        # classes to call 'super'
         
     | 
| 
      
 44 
     | 
    
         
            +
                        def has_through_method_missing?(m)
         
     | 
| 
      
 45 
     | 
    
         
            +
                        end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                        # Empty implementation of find_through_method_missing to allow for
         
     | 
| 
      
 48 
     | 
    
         
            +
                        # classes to call 'super'
         
     | 
| 
      
 49 
     | 
    
         
            +
                        def find_through_method_missing(m, args)
         
     | 
| 
      
 50 
     | 
    
         
            +
                        end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                        # Resolves the given method using {#has_through_method_missing?}
         
     | 
| 
      
 53 
     | 
    
         
            +
                        def respond_to_missing?(m, include_private)
         
     | 
| 
      
 54 
     | 
    
         
            +
                            has_through_method_missing?(m) || super
         
     | 
| 
      
 55 
     | 
    
         
            +
                        end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                        # Resolves the given method using {#find_through_method_missing}
         
     | 
| 
      
 58 
     | 
    
         
            +
                        def method_missing(m, *args)
         
     | 
| 
      
 59 
     | 
    
         
            +
                            find_through_method_missing(m, args) || super
         
     | 
| 
      
 60 
     | 
    
         
            +
                        end
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
       3 
63 
     | 
    
         
             
                    # Generic implementation to create suffixed accessors for child objects
         
     | 
| 
       4 
64 
     | 
    
         
             
                    # on a class
         
     | 
| 
       5 
65 
     | 
    
         
             
                    #
         
     | 
| 
         @@ -45,32 +105,34 @@ module MetaRuby 
     | 
|
| 
       45 
105 
     | 
    
         
             
                    #   object.add_state 'my'
         
     | 
| 
       46 
106 
     | 
    
         
             
                    #   object.my_state # will resolve the 'my' state
         
     | 
| 
       47 
107 
     | 
    
         
             
                    #
         
     | 
| 
       48 
     | 
    
         
            -
                    def self.find_through_method_missing(object, m, args,  
     | 
| 
       49 
     | 
    
         
            -
                         
     | 
| 
       50 
     | 
    
         
            -
                        if suffixes.last.kind_of?(Hash)
         
     | 
| 
       51 
     | 
    
         
            -
                            suffix_match.merge!(suffixes.pop)
         
     | 
| 
       52 
     | 
    
         
            -
                        end
         
     | 
| 
       53 
     | 
    
         
            -
                        suffixes.each do |name|
         
     | 
| 
       54 
     | 
    
         
            -
                            suffix_match[name] = "find_#{name}"
         
     | 
| 
       55 
     | 
    
         
            -
                        end
         
     | 
| 
      
 108 
     | 
    
         
            +
                    def self.find_through_method_missing(object, m, args, suffix_match)
         
     | 
| 
      
 109 
     | 
    
         
            +
                        return false if m == :to_ary
         
     | 
| 
       56 
110 
     | 
    
         | 
| 
       57 
111 
     | 
    
         
             
                        m = m.to_s
         
     | 
| 
       58 
112 
     | 
    
         
             
                        suffix_match.each do |s, find_method_name|
         
     | 
| 
       59 
     | 
    
         
            -
                            if m 
     | 
| 
       60 
     | 
    
         
            -
                                 
     | 
| 
       61 
     | 
    
         
            -
                            elsif m =~ /(.*)_#{s}$/
         
     | 
| 
       62 
     | 
    
         
            -
                                name = $1
         
     | 
| 
      
 113 
     | 
    
         
            +
                            if m.end_with?(s)
         
     | 
| 
      
 114 
     | 
    
         
            +
                                name = m[0, m.size - s.size]
         
     | 
| 
       63 
115 
     | 
    
         
             
                                if !args.empty?
         
     | 
| 
       64 
116 
     | 
    
         
             
                                    raise ArgumentError, "expected zero arguments to #{m}, got #{args.size}", caller(4)
         
     | 
| 
       65 
     | 
    
         
            -
                                elsif found = object.send(find_method_name, name)
         
     | 
| 
       66 
     | 
    
         
            -
                                    return found
         
     | 
| 
       67 
117 
     | 
    
         
             
                                else
         
     | 
| 
       68 
     | 
    
         
            -
                                     
     | 
| 
       69 
     | 
    
         
            -
                                    raise NoMethodError.new(msg, m), msg, caller(4)
         
     | 
| 
      
 118 
     | 
    
         
            +
                                    return object.send(find_method_name, name)
         
     | 
| 
       70 
119 
     | 
    
         
             
                                end
         
     | 
| 
       71 
120 
     | 
    
         
             
                            end
         
     | 
| 
       72 
121 
     | 
    
         
             
                        end
         
     | 
| 
       73 
122 
     | 
    
         
             
                        nil
         
     | 
| 
       74 
123 
     | 
    
         
             
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    def self.has_through_method_missing?(object, m, suffix_match)
         
     | 
| 
      
 126 
     | 
    
         
            +
                        return false if m == :to_ary
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                        m = m.to_s
         
     | 
| 
      
 129 
     | 
    
         
            +
                        suffix_match.each do |s, has_method_name|
         
     | 
| 
      
 130 
     | 
    
         
            +
                            if m.end_with?(s)
         
     | 
| 
      
 131 
     | 
    
         
            +
                                name = m[0, m.size - s.size]
         
     | 
| 
      
 132 
     | 
    
         
            +
                                return !!object.send(has_method_name, name)
         
     | 
| 
      
 133 
     | 
    
         
            +
                            end
         
     | 
| 
      
 134 
     | 
    
         
            +
                        end
         
     | 
| 
      
 135 
     | 
    
         
            +
                        false
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
       75 
137 
     | 
    
         
             
                end
         
     | 
| 
       76 
138 
     | 
    
         
             
            end
         
     | 
    
        data/lib/metaruby/gui.rb
    CHANGED
    
    | 
         @@ -4,6 +4,7 @@ require 'kramdown' 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require 'pp'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'metaruby/gui/html'
         
     | 
| 
       6 
6 
     | 
    
         
             
            require 'metaruby/gui/ruby_constants_item_model'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'metaruby/gui/model_hierarchy'
         
     | 
| 
       7 
8 
     | 
    
         
             
            require 'metaruby/gui/rendering_manager'
         
     | 
| 
       8 
9 
     | 
    
         
             
            require 'metaruby/gui/model_browser'
         
     | 
| 
       9 
10 
     | 
    
         
             
            require 'metaruby/gui/model_selector'
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            .message { font-size:  
     | 
| 
       2 
     | 
    
         
            -
            .backtrace_links { font-size:  
     | 
| 
       3 
     | 
    
         
            -
            .backtrace { font-size:  
     | 
| 
       4 
     | 
    
         
            -
            .backtrace_summary { font-size:  
     | 
| 
      
 1 
     | 
    
         
            +
            .message { font-size: 100%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
         
     | 
| 
      
 2 
     | 
    
         
            +
            .backtrace_links { font-size: 100%; margin-left: 2em;}
         
     | 
| 
      
 3 
     | 
    
         
            +
            .backtrace { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
         
     | 
| 
      
 4 
     | 
    
         
            +
            .backtrace_summary { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
         
     | 
| 
       5 
5 
     | 
    
         
             
            .user_file { font-weight: bold; }
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
         @@ -57,14 +57,19 @@ module MetaRuby 
     | 
|
| 
       57 
57 
     | 
    
         
             
                        # A Page object tunes to create URIs for objects that are suitable
         
     | 
| 
       58 
58 
     | 
    
         
             
                        # for {#model_selector}
         
     | 
| 
       59 
59 
     | 
    
         
             
                        class Page < HTML::Page
         
     | 
| 
      
 60 
     | 
    
         
            +
                            def initialize(model_selector, display_page)
         
     | 
| 
      
 61 
     | 
    
         
            +
                                super(display_page)
         
     | 
| 
      
 62 
     | 
    
         
            +
                                @model_selector = model_selector
         
     | 
| 
      
 63 
     | 
    
         
            +
                            end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
       60 
65 
     | 
    
         
             
                            # Overloaded from {HTML::Page} to resolve object paths (in the
         
     | 
| 
       61 
66 
     | 
    
         
             
                            #   constant hierarchy, e.g. A::B::C) into the corresponding
         
     | 
| 
       62 
67 
     | 
    
         
             
                            #   path expected by {#model_selector} (e.g. /A/B/C)
         
     | 
| 
       63 
68 
     | 
    
         
             
                            def uri_for(object)
         
     | 
| 
       64 
     | 
    
         
            -
                                if  
     | 
| 
       65 
     | 
    
         
            -
                                     
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
                                if resolver = @model_selector.find_resolver_from_model(object)
         
     | 
| 
      
 70 
     | 
    
         
            +
                                    "/" + resolver.split_name(object).join("/")
         
     | 
| 
      
 71 
     | 
    
         
            +
                                else
         
     | 
| 
      
 72 
     | 
    
         
            +
                                    super
         
     | 
| 
       68 
73 
     | 
    
         
             
                                end
         
     | 
| 
       69 
74 
     | 
    
         
             
                            end
         
     | 
| 
       70 
75 
     | 
    
         
             
                        end
         
     | 
| 
         @@ -151,9 +156,11 @@ module MetaRuby 
     | 
|
| 
       151 
156 
     | 
    
         
             
                        #   models might be submodels of various types at the same time (as
         
     | 
| 
       152 
157 
     | 
    
         
             
                        #   e.g. when both a model and its supermodel are registered here).
         
     | 
| 
       153 
158 
     | 
    
         
             
                        #   The one with the highest priority will be used.
         
     | 
| 
       154 
     | 
    
         
            -
                        def register_type( 
     | 
| 
       155 
     | 
    
         
            -
                            model_selector.register_type( 
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
      
 159 
     | 
    
         
            +
                        def register_type(root_model, rendering_class, name, priority = 0, categories: [], resolver: ModelHierarchy::Resolver.new)
         
     | 
| 
      
 160 
     | 
    
         
            +
                            model_selector.register_type(
         
     | 
| 
      
 161 
     | 
    
         
            +
                                root_model, name, priority,
         
     | 
| 
      
 162 
     | 
    
         
            +
                                categories: categories, resolver: resolver)
         
     | 
| 
      
 163 
     | 
    
         
            +
                            manager.register_type(root_model, rendering_class)
         
     | 
| 
       157 
164 
     | 
    
         
             
                        end
         
     | 
| 
       158 
165 
     | 
    
         | 
| 
       159 
166 
     | 
    
         
             
                        # Sets up the widgets that form the central part of the browser
         
     | 
| 
         @@ -181,7 +188,7 @@ module MetaRuby 
     | 
|
| 
       181 
188 
     | 
    
         
             
                            end
         
     | 
| 
       182 
189 
     | 
    
         
             
                            splitter.add_widget(display)
         
     | 
| 
       183 
190 
     | 
    
         
             
                            splitter.set_stretch_factor(1, 2)
         
     | 
| 
       184 
     | 
    
         
            -
                            self.page = Page.new(display.page)
         
     | 
| 
      
 191 
     | 
    
         
            +
                            self.page = Page.new(@model_selector, display.page)
         
     | 
| 
       185 
192 
     | 
    
         | 
| 
       186 
193 
     | 
    
         
             
                            model_selector.connect(SIGNAL('model_selected(QVariant)')) do |mod|
         
     | 
| 
       187 
194 
     | 
    
         
             
                                mod = mod.to_ruby
         
     | 
| 
         @@ -194,20 +201,28 @@ module MetaRuby 
     | 
|
| 
       194 
201 
     | 
    
         
             
                        #
         
     | 
| 
       195 
202 
     | 
    
         
             
                        # @param [Page] page the new page object
         
     | 
| 
       196 
203 
     | 
    
         
             
                        def page=(page)
         
     | 
| 
       197 
     | 
    
         
            -
                             
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
       199 
     | 
    
         
            -
                                 
     | 
| 
       200 
     | 
    
         
            -
             
     | 
| 
       201 
     | 
    
         
            -
                                    path = path.split('/')[1..-1]
         
     | 
| 
       202 
     | 
    
         
            -
                                    select_by_path(*path)
         
     | 
| 
       203 
     | 
    
         
            -
                                end
         
     | 
| 
      
 204 
     | 
    
         
            +
                            if @page
         
     | 
| 
      
 205 
     | 
    
         
            +
                                disconnect(@page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
         
     | 
| 
      
 206 
     | 
    
         
            +
                                disconnect(@page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
         
     | 
| 
      
 207 
     | 
    
         
            +
                                disconnect(@page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
         
     | 
| 
       204 
208 
     | 
    
         
             
                            end
         
     | 
| 
      
 209 
     | 
    
         
            +
                            manager.page = page
         
     | 
| 
      
 210 
     | 
    
         
            +
                            connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
         
     | 
| 
       205 
211 
     | 
    
         
             
                            connect(page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
         
     | 
| 
       206 
212 
     | 
    
         
             
                            connect(page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
         
     | 
| 
       207 
213 
     | 
    
         
             
                            connect(manager, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
         
     | 
| 
       208 
214 
     | 
    
         
             
                            @page = page
         
     | 
| 
       209 
215 
     | 
    
         
             
                        end
         
     | 
| 
       210 
216 
     | 
    
         | 
| 
      
 217 
     | 
    
         
            +
                        def linkClicked(url)
         
     | 
| 
      
 218 
     | 
    
         
            +
                            if url.scheme == "link"
         
     | 
| 
      
 219 
     | 
    
         
            +
                                path = url.path
         
     | 
| 
      
 220 
     | 
    
         
            +
                                path = path.split('/')[1..-1]
         
     | 
| 
      
 221 
     | 
    
         
            +
                                select_by_path(*path)
         
     | 
| 
      
 222 
     | 
    
         
            +
                            end
         
     | 
| 
      
 223 
     | 
    
         
            +
                        end
         
     | 
| 
      
 224 
     | 
    
         
            +
                        slots 'linkClicked(const QUrl&)'
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
       211 
226 
     | 
    
         
             
                        signals 'fileOpenClicked(const QUrl&)'
         
     | 
| 
       212 
227 
     | 
    
         | 
| 
       213 
228 
     | 
    
         
             
                        # Call to render the given model
         
     | 
| 
         @@ -239,16 +254,16 @@ module MetaRuby 
     | 
|
| 
       239 
254 
     | 
    
         
             
                        end
         
     | 
| 
       240 
255 
     | 
    
         
             
                        slots 'update_exceptions()'
         
     | 
| 
       241 
256 
     | 
    
         | 
| 
       242 
     | 
    
         
            -
                        # (see ModelSelector# 
     | 
| 
      
 257 
     | 
    
         
            +
                        # (see ModelSelector#select_by_model)
         
     | 
| 
       243 
258 
     | 
    
         
             
                        def select_by_path(*path)
         
     | 
| 
       244 
259 
     | 
    
         
             
                            if model_selector.select_by_path(*path)
         
     | 
| 
       245 
260 
     | 
    
         
             
                                push_to_history(path)
         
     | 
| 
       246 
261 
     | 
    
         
             
                            end
         
     | 
| 
       247 
262 
     | 
    
         
             
                        end
         
     | 
| 
       248 
263 
     | 
    
         | 
| 
       249 
     | 
    
         
            -
                        # (see ModelSelector# 
     | 
| 
       250 
     | 
    
         
            -
                        def  
     | 
| 
       251 
     | 
    
         
            -
                            if model_selector. 
     | 
| 
      
 264 
     | 
    
         
            +
                        # (see ModelSelector#select_by_model)
         
     | 
| 
      
 265 
     | 
    
         
            +
                        def select_by_model(model)
         
     | 
| 
      
 266 
     | 
    
         
            +
                            if model_selector.select_by_model(model)
         
     | 
| 
       252 
267 
     | 
    
         
             
                                push_to_history(model)
         
     | 
| 
       253 
268 
     | 
    
         
             
                            end
         
     | 
| 
       254 
269 
     | 
    
         
             
                        end
         
     | 
| 
         @@ -289,7 +304,7 @@ module MetaRuby 
     | 
|
| 
       289 
304 
     | 
    
         
             
                        def select_by_history_element(h)
         
     | 
| 
       290 
305 
     | 
    
         
             
                            if h.respond_to?(:to_ary)
         
     | 
| 
       291 
306 
     | 
    
         
             
                                select_by_path(*h)
         
     | 
| 
       292 
     | 
    
         
            -
                            else  
     | 
| 
      
 307 
     | 
    
         
            +
                            else select_by_model(h)
         
     | 
| 
       293 
308 
     | 
    
         
             
                            end
         
     | 
| 
       294 
309 
     | 
    
         
             
                        end
         
     | 
| 
       295 
310 
     | 
    
         | 
| 
         @@ -0,0 +1,267 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MetaRuby
         
     | 
| 
      
 2 
     | 
    
         
            +
                module GUI
         
     | 
| 
      
 3 
     | 
    
         
            +
                    class ModelHierarchy < Qt::StandardItemModel
         
     | 
| 
      
 4 
     | 
    
         
            +
                        Metadata = Struct.new :name, :search_key, :categories do
         
     | 
| 
      
 5 
     | 
    
         
            +
                            def merge(other)
         
     | 
| 
      
 6 
     | 
    
         
            +
                                a, b = search_key, other.search_key.dup
         
     | 
| 
      
 7 
     | 
    
         
            +
                                if a.size < b.size
         
     | 
| 
      
 8 
     | 
    
         
            +
                                    a, b = b, a
         
     | 
| 
      
 9 
     | 
    
         
            +
                                end
         
     | 
| 
      
 10 
     | 
    
         
            +
                                b.size.times do |i|
         
     | 
| 
      
 11 
     | 
    
         
            +
                                    a[i] |= b[i]
         
     | 
| 
      
 12 
     | 
    
         
            +
                                end
         
     | 
| 
      
 13 
     | 
    
         
            +
                                self.search_key = a
         
     | 
| 
      
 14 
     | 
    
         
            +
                                self.categories = categories | other.categories
         
     | 
| 
      
 15 
     | 
    
         
            +
                            end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                            def to_user_role
         
     | 
| 
      
 18 
     | 
    
         
            +
                                categories = self.categories.to_a.join(",")
         
     | 
| 
      
 19 
     | 
    
         
            +
                                search_key = self.search_key.map do |level_keys|
         
     | 
| 
      
 20 
     | 
    
         
            +
                                    level_keys.join(",")
         
     | 
| 
      
 21 
     | 
    
         
            +
                                end.join(",;,")
         
     | 
| 
      
 22 
     | 
    
         
            +
                                ",#{categories},;,#{search_key},"
         
     | 
| 
      
 23 
     | 
    
         
            +
                            end
         
     | 
| 
      
 24 
     | 
    
         
            +
                        end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                        class Resolver
         
     | 
| 
      
 27 
     | 
    
         
            +
                            def initialize(root_model)
         
     | 
| 
      
 28 
     | 
    
         
            +
                                @root_model = root_model
         
     | 
| 
      
 29 
     | 
    
         
            +
                            end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                            def split_name(model)
         
     | 
| 
      
 32 
     | 
    
         
            +
                                name = model.name
         
     | 
| 
      
 33 
     | 
    
         
            +
                                split = model.name.split('::')
         
     | 
| 
      
 34 
     | 
    
         
            +
                                if name.start_with?('::')
         
     | 
| 
      
 35 
     | 
    
         
            +
                                    split[1..-1]
         
     | 
| 
      
 36 
     | 
    
         
            +
                                else split
         
     | 
| 
      
 37 
     | 
    
         
            +
                                end
         
     | 
| 
      
 38 
     | 
    
         
            +
                            end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                            def each_submodel(model)
         
     | 
| 
      
 41 
     | 
    
         
            +
                                if model == @root_model
         
     | 
| 
      
 42 
     | 
    
         
            +
                                    model.each_submodel do |m|
         
     | 
| 
      
 43 
     | 
    
         
            +
                                        yield(m, !m.name)
         
     | 
| 
      
 44 
     | 
    
         
            +
                                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                                end
         
     | 
| 
      
 46 
     | 
    
         
            +
                            end
         
     | 
| 
      
 47 
     | 
    
         
            +
                        end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                        def initialize
         
     | 
| 
      
 50 
     | 
    
         
            +
                            super()
         
     | 
| 
      
 51 
     | 
    
         
            +
                            @root_models = Array.new
         
     | 
| 
      
 52 
     | 
    
         
            +
                        end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                        # Find the resolver object that has been responsible for a given
         
     | 
| 
      
 55 
     | 
    
         
            +
                        # object's discovery
         
     | 
| 
      
 56 
     | 
    
         
            +
                        #
         
     | 
| 
      
 57 
     | 
    
         
            +
                        # @return [Object,nil]
         
     | 
| 
      
 58 
     | 
    
         
            +
                        def find_resolver_from_model(model)
         
     | 
| 
      
 59 
     | 
    
         
            +
                            @resolver_from_model[model]
         
     | 
| 
      
 60 
     | 
    
         
            +
                        end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                        # Returns the path to the given model or nil if it is not registered
         
     | 
| 
      
 63 
     | 
    
         
            +
                        def find_path_from_model(model)
         
     | 
| 
      
 64 
     | 
    
         
            +
                            if resolver = find_resolver_from_model(model)
         
     | 
| 
      
 65 
     | 
    
         
            +
                                resolver.split_name(model)
         
     | 
| 
      
 66 
     | 
    
         
            +
                            end
         
     | 
| 
      
 67 
     | 
    
         
            +
                        end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                        RootModel = Struct.new :model, :priority, :categories, :resolver
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                        def add_root(root_model, priority, categories: [], resolver: Resolver.new(root_model))
         
     | 
| 
      
 72 
     | 
    
         
            +
                            @root_models << RootModel.new(root_model, priority, categories, resolver)
         
     | 
| 
      
 73 
     | 
    
         
            +
                        end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                        # Refresh the model to match the current hierarchy that starts with
         
     | 
| 
      
 76 
     | 
    
         
            +
                        # this object's root model
         
     | 
| 
      
 77 
     | 
    
         
            +
                        def reload
         
     | 
| 
      
 78 
     | 
    
         
            +
                            begin_reset_model
         
     | 
| 
      
 79 
     | 
    
         
            +
                            clear
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                            @items_to_models = Hash.new
         
     | 
| 
      
 82 
     | 
    
         
            +
                            @models_to_items = Hash.new
         
     | 
| 
      
 83 
     | 
    
         
            +
                            @names_to_item = Hash.new
         
     | 
| 
      
 84 
     | 
    
         
            +
                            @items_metadata = Hash[self => Metadata.new([], [], Set.new)]
         
     | 
| 
      
 85 
     | 
    
         
            +
                            @resolver_from_model = Hash.new
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                            seen = Set.new
         
     | 
| 
      
 88 
     | 
    
         
            +
                            sorted_roots = @root_models.
         
     | 
| 
      
 89 
     | 
    
         
            +
                                sort_by(&:priority).reverse
         
     | 
| 
      
 90 
     | 
    
         
            +
                            
         
     | 
| 
      
 91 
     | 
    
         
            +
                            sorted_roots.each do |root_model|
         
     | 
| 
      
 92 
     | 
    
         
            +
                                models = discover_model_hierarchy(root_model.model, root_model.categories, root_model.resolver, seen)
         
     | 
| 
      
 93 
     | 
    
         
            +
                                models.each do |m|
         
     | 
| 
      
 94 
     | 
    
         
            +
                                    @resolver_from_model[m] = root_model.resolver
         
     | 
| 
      
 95 
     | 
    
         
            +
                                end
         
     | 
| 
      
 96 
     | 
    
         
            +
                            end
         
     | 
| 
      
 97 
     | 
    
         
            +
                            
         
     | 
| 
      
 98 
     | 
    
         
            +
                            rowCount.times do |row|
         
     | 
| 
      
 99 
     | 
    
         
            +
                                compute_and_store_metadata(item(row))
         
     | 
| 
      
 100 
     | 
    
         
            +
                            end
         
     | 
| 
      
 101 
     | 
    
         
            +
                            self.horizontal_header_labels = [""]
         
     | 
| 
      
 102 
     | 
    
         
            +
                        ensure
         
     | 
| 
      
 103 
     | 
    
         
            +
                            end_reset_model
         
     | 
| 
      
 104 
     | 
    
         
            +
                        end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                        # Returns the model that matches the QStandardItem
         
     | 
| 
      
 107 
     | 
    
         
            +
                        #
         
     | 
| 
      
 108 
     | 
    
         
            +
                        # @return [Object,nil]
         
     | 
| 
      
 109 
     | 
    
         
            +
                        def find_model_from_item(item)
         
     | 
| 
      
 110 
     | 
    
         
            +
                            @items_to_models[item]
         
     | 
| 
      
 111 
     | 
    
         
            +
                        end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                        # Returns the model that matches the QModelIndex
         
     | 
| 
      
 114 
     | 
    
         
            +
                        #
         
     | 
| 
      
 115 
     | 
    
         
            +
                        # @return [Object,nil]
         
     | 
| 
      
 116 
     | 
    
         
            +
                        def find_model_from_index(index)
         
     | 
| 
      
 117 
     | 
    
         
            +
                            @items_to_models[
         
     | 
| 
      
 118 
     | 
    
         
            +
                                item_from_index(index)]
         
     | 
| 
      
 119 
     | 
    
         
            +
                        end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 122 
     | 
    
         
            +
                        #
         
     | 
| 
      
 123 
     | 
    
         
            +
                        # Compute each item's metadata and stores it in UserRole. The
         
     | 
| 
      
 124 
     | 
    
         
            +
                        # metadata is stored as
         
     | 
| 
      
 125 
     | 
    
         
            +
                        # "Category0|Category1;name/with/slashes/between/parts". This is
         
     | 
| 
      
 126 
     | 
    
         
            +
                        # meant to be used in a seach/filter function.
         
     | 
| 
      
 127 
     | 
    
         
            +
                        def compute_and_store_metadata(item)
         
     | 
| 
      
 128 
     | 
    
         
            +
                            current = @items_metadata[item]
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                            item.rowCount.times do |row|
         
     | 
| 
      
 131 
     | 
    
         
            +
                                current.merge compute_and_store_metadata(item.child(row))
         
     | 
| 
      
 132 
     | 
    
         
            +
                            end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                            item.set_data(Qt::Variant.new(current.to_user_role), Qt::UserRole)
         
     | 
| 
      
 135 
     | 
    
         
            +
                            current
         
     | 
| 
      
 136 
     | 
    
         
            +
                        end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 139 
     | 
    
         
            +
                        #
         
     | 
| 
      
 140 
     | 
    
         
            +
                        # Register a model in the hierarchy
         
     | 
| 
      
 141 
     | 
    
         
            +
                        def register_model(model, model_categories, resolver)
         
     | 
| 
      
 142 
     | 
    
         
            +
                            name = resolver.split_name(model)
         
     | 
| 
      
 143 
     | 
    
         
            +
                            if !name || name.empty?
         
     | 
| 
      
 144 
     | 
    
         
            +
                                #raise ArgumentError, "cannot resolve #{model.name}"
         
     | 
| 
      
 145 
     | 
    
         
            +
                                puts "Cannot resolve #{model}"
         
     | 
| 
      
 146 
     | 
    
         
            +
                                return
         
     | 
| 
      
 147 
     | 
    
         
            +
                            end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                            context = name[0..-2].inject(self) do |item, name|
         
     | 
| 
      
 150 
     | 
    
         
            +
                                resolve_namespace(item, name)
         
     | 
| 
      
 151 
     | 
    
         
            +
                            end
         
     | 
| 
      
 152 
     | 
    
         
            +
                            if !(item = find_item_child_by_name(context, name.last))
         
     | 
| 
      
 153 
     | 
    
         
            +
                                item = Qt::StandardItem.new(name.last)
         
     | 
| 
      
 154 
     | 
    
         
            +
                                context.append_row(item)
         
     | 
| 
      
 155 
     | 
    
         
            +
                                (@names_to_item[context] ||= Hash.new)[name.last] = item
         
     | 
| 
      
 156 
     | 
    
         
            +
                            end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                            item.flags = Qt::ItemIsEnabled
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                            @items_metadata[item] = Metadata.new(name, name.map { |n| [n] }, model_categories)
         
     | 
| 
      
 161 
     | 
    
         
            +
                            @items_to_models[item] = model
         
     | 
| 
      
 162 
     | 
    
         
            +
                            @models_to_items[model] = item
         
     | 
| 
      
 163 
     | 
    
         
            +
                        end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 166 
     | 
    
         
            +
                        def find_item_child_by_name(item, name)
         
     | 
| 
      
 167 
     | 
    
         
            +
                            if context = @names_to_item[item]
         
     | 
| 
      
 168 
     | 
    
         
            +
                                context[name]
         
     | 
| 
      
 169 
     | 
    
         
            +
                            end
         
     | 
| 
      
 170 
     | 
    
         
            +
                        end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                        # Returns the QStandardItem object that sits at the given path
         
     | 
| 
      
 173 
     | 
    
         
            +
                        #
         
     | 
| 
      
 174 
     | 
    
         
            +
                        # @return [Qt::StandardItem,nil]
         
     | 
| 
      
 175 
     | 
    
         
            +
                        def find_item_by_path(*path)
         
     | 
| 
      
 176 
     | 
    
         
            +
                            path.inject(self) do |parent_item, name|
         
     | 
| 
      
 177 
     | 
    
         
            +
                                return if !name
         
     | 
| 
      
 178 
     | 
    
         
            +
                                if names = @names_to_item[parent_item]
         
     | 
| 
      
 179 
     | 
    
         
            +
                                    names[name]
         
     | 
| 
      
 180 
     | 
    
         
            +
                                end
         
     | 
| 
      
 181 
     | 
    
         
            +
                            end
         
     | 
| 
      
 182 
     | 
    
         
            +
                        end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                        # Returns the ModelIndex object that sits at the given path
         
     | 
| 
      
 185 
     | 
    
         
            +
                        #
         
     | 
| 
      
 186 
     | 
    
         
            +
                        # @return [Qt::ModelIndex,nil]
         
     | 
| 
      
 187 
     | 
    
         
            +
                        def find_index_by_path(*path)
         
     | 
| 
      
 188 
     | 
    
         
            +
                            if item = find_item_by_path(*path)
         
     | 
| 
      
 189 
     | 
    
         
            +
                                item.index
         
     | 
| 
      
 190 
     | 
    
         
            +
                            end
         
     | 
| 
      
 191 
     | 
    
         
            +
                        end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                        # Returns the QStandardItem object that represents the given model
         
     | 
| 
      
 194 
     | 
    
         
            +
                        def find_item_by_model(model)
         
     | 
| 
      
 195 
     | 
    
         
            +
                            @models_to_items[model]
         
     | 
| 
      
 196 
     | 
    
         
            +
                        end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                        # Returns the QModelIndex object that represents the given model
         
     | 
| 
      
 199 
     | 
    
         
            +
                        def find_index_by_model(model)
         
     | 
| 
      
 200 
     | 
    
         
            +
                            if item = find_item_by_model(model)
         
     | 
| 
      
 201 
     | 
    
         
            +
                                item.index
         
     | 
| 
      
 202 
     | 
    
         
            +
                            end
         
     | 
| 
      
 203 
     | 
    
         
            +
                        end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 206 
     | 
    
         
            +
                        #
         
     | 
| 
      
 207 
     | 
    
         
            +
                        # Register a model and its whole submodels hierarchy
         
     | 
| 
      
 208 
     | 
    
         
            +
                        def discover_model_hierarchy(root_model, categories, resolver, seen)
         
     | 
| 
      
 209 
     | 
    
         
            +
                            discovered = Array.new
         
     | 
| 
      
 210 
     | 
    
         
            +
                            queue = [root_model]
         
     | 
| 
      
 211 
     | 
    
         
            +
                            categories = categories.to_set
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                            while !queue.empty?
         
     | 
| 
      
 214 
     | 
    
         
            +
                                m = queue.shift
         
     | 
| 
      
 215 
     | 
    
         
            +
                                next if seen.include?(m)
         
     | 
| 
      
 216 
     | 
    
         
            +
                                seen << m
         
     | 
| 
      
 217 
     | 
    
         
            +
                                discovered << m
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                                register_model(m, categories, resolver)
         
     | 
| 
      
 220 
     | 
    
         
            +
                                resolver.each_submodel(m) do |model, excluded|
         
     | 
| 
      
 221 
     | 
    
         
            +
                                    raise if model.kind_of?(String)
         
     | 
| 
      
 222 
     | 
    
         
            +
                                    if excluded
         
     | 
| 
      
 223 
     | 
    
         
            +
                                        seen << model
         
     | 
| 
      
 224 
     | 
    
         
            +
                                    else
         
     | 
| 
      
 225 
     | 
    
         
            +
                                        queue << model
         
     | 
| 
      
 226 
     | 
    
         
            +
                                    end
         
     | 
| 
      
 227 
     | 
    
         
            +
                                end
         
     | 
| 
      
 228 
     | 
    
         
            +
                            end
         
     | 
| 
      
 229 
     | 
    
         
            +
                            discovered
         
     | 
| 
      
 230 
     | 
    
         
            +
                        end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 233 
     | 
    
         
            +
                        #
         
     | 
| 
      
 234 
     | 
    
         
            +
                        # Find or create the StandardItem that represents a root in the
         
     | 
| 
      
 235 
     | 
    
         
            +
                        # hierarchy
         
     | 
| 
      
 236 
     | 
    
         
            +
                        #
         
     | 
| 
      
 237 
     | 
    
         
            +
                        # @param [String] name the name of the namespace
         
     | 
| 
      
 238 
     | 
    
         
            +
                        # @return [Qt::StandardItem]
         
     | 
| 
      
 239 
     | 
    
         
            +
                        def resolve_root_namespace(name)
         
     | 
| 
      
 240 
     | 
    
         
            +
                            resolve_namespace(self, name)
         
     | 
| 
      
 241 
     | 
    
         
            +
                        end
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 244 
     | 
    
         
            +
                        #
         
     | 
| 
      
 245 
     | 
    
         
            +
                        # Find or create the StandardItem that represents a namespace in the
         
     | 
| 
      
 246 
     | 
    
         
            +
                        # hierarchy
         
     | 
| 
      
 247 
     | 
    
         
            +
                        #
         
     | 
| 
      
 248 
     | 
    
         
            +
                        # @param [Qt::StandardItem] parent_item the parent item
         
     | 
| 
      
 249 
     | 
    
         
            +
                        # @param [String] name the name of the namespace
         
     | 
| 
      
 250 
     | 
    
         
            +
                        # @return [Qt::StandardItem]
         
     | 
| 
      
 251 
     | 
    
         
            +
                        def resolve_namespace(parent_item, name)
         
     | 
| 
      
 252 
     | 
    
         
            +
                            if item = find_item_child_by_name(parent_item, name)
         
     | 
| 
      
 253 
     | 
    
         
            +
                                item
         
     | 
| 
      
 254 
     | 
    
         
            +
                            else
         
     | 
| 
      
 255 
     | 
    
         
            +
                                item = Qt::StandardItem.new(name)
         
     | 
| 
      
 256 
     | 
    
         
            +
                                item.flags = 0
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
                                parent_name = @items_metadata[parent_item].name
         
     | 
| 
      
 259 
     | 
    
         
            +
                                @items_metadata[item] = Metadata.new(parent_name + [name], [], Set.new)
         
     | 
| 
      
 260 
     | 
    
         
            +
                                parent_item.append_row item
         
     | 
| 
      
 261 
     | 
    
         
            +
                                (@names_to_item[parent_item] ||= Hash.new)[name] = item
         
     | 
| 
      
 262 
     | 
    
         
            +
                            end
         
     | 
| 
      
 263 
     | 
    
         
            +
                        end
         
     | 
| 
      
 264 
     | 
    
         
            +
                    end
         
     | 
| 
      
 265 
     | 
    
         
            +
                end
         
     | 
| 
      
 266 
     | 
    
         
            +
            end
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     |