summaryrefslogtreecommitdiff
path: root/lib/rdoc/ri/ri_cache.rb
blob: f2cdbf6f38dfbb01b143f63b77eb2cc49e8226d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
module RI

  class ClassEntry

    attr_reader :name
    attr_reader :path_name
    
    def initialize(path_name, name, in_class)
      @path_name = path_name
      @name = name
      @in_class = in_class
      @class_methods    = []
      @instance_methods = []
      @inferior_classes = []
    end

    # read in our methods and any classes
    # and modules in our namespace. Methods are
    # stored in files called name-c|i.yaml,
    # where the 'name' portion is the external
    # form of the method name and the c|i is a class|instance
    # flag

    def load_from(dir)
      Dir.foreach(dir) do |name|
        next if name =~ /^\./

        # convert from external to internal form, and
        # extract the instance/class flag

        if name =~ /^(.*?)-(c|i).yaml$/
          external_name = $1
          is_class_method = $2 == "c"
          internal_name = external_name
          list = is_class_method ? @class_methods : @instance_methods
          path = File.join(dir, name)
          list << MethodEntry.new(path, internal_name, is_class_method, self)
        else
          full_name = File.join(dir, name)
          if File.directory?(full_name)
            inf_class = ClassEntry.new(full_name, name, self)
            inf_class.load_from(full_name)
            @inferior_classes << inf_class
          end
        end
      end
    end

    # Return a list of any classes or modules that we contain
    # that match a given string

    def contained_modules_matching(name)
      @inferior_classes.find_all {|c| c.name[name]}
    end

    # return the list of local methods matching name
    # We're split into two because we need distinct behavior
    # when called from the toplevel
    def methods_matching(name, is_class_method)
      local_methods_matching(name, is_class_method)
    end

    # Find methods matching 'name' in ourselves and in
    # any classes we contain
    def recursively_find_methods_matching(name, is_class_method)
      res = local_methods_matching(name, is_class_method)
      @inferior_classes.each do |c|
        res.concat(c.recursively_find_methods_matching(name, is_class_method))
      end
      res
    end


    # Return our full name
    def full_name
      res = @in_class.full_name
      res << "::" unless res.empty?
      res << @name
    end

    private

    # Return a list of all our methods matching a given string.
    # Is +is_class_methods+ if 'nil', we don't care if the method
    # is a class method or not, otherwise we only return
    # those methods that match
    def local_methods_matching(name, is_class_method)

      list = case is_class_method
             when nil then  @class_methods + @instance_methods
             when true then @class_methods
             when false then @instance_methods
             else fail "Unknown is_class_method"
             end

      list.find_all {|m| m.name[name]}
    end
  end

  # A TopLevelEntry is like a class entry, but when asked to search
  # for methods searches all classes, not just itself

  class TopLevelEntry < ClassEntry
    def methods_matching(name, is_class_method)
      res = recursively_find_methods_matching(name, is_class_method)
    end

    def full_name
      ""
    end
  end

  class MethodEntry
    attr_reader :name
    attr_reader :path_name

    def initialize(path_name, name, is_class_method, in_class)
      @path_name = path_name
      @name = name
      @is_class_method = is_class_method
      @in_class = in_class
    end

    def full_name
      res = @in_class.full_name
      unless res.empty?
        if @is_class_method
          res << "::"
        else
          res << "#"
        end
      end
      res << @name
    end
  end

  # We represent everything know about all 'ri' files
  # accessible to this program

  class RiCache

    attr_reader :toplevel

    def initialize(dirs)
      # At the top level we have a dummy module holding the
      # overall namespace
      @toplevel = TopLevelEntry.new('', '::', nil)

      dirs.each do |dir|
        @toplevel.load_from(dir)
      end
    end

  end
end