Module: Autocode

Defined in:
lib/autocode.rb

Class Method Summary collapse

Class Method Details

.extended(mod) ⇒ Object



3
4
5
# File 'lib/autocode.rb', line 3

def self.extended( mod )
  included(mod)
end

.included(mod) ⇒ Object



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
# File 'lib/autocode.rb', line 7

def self.included( mod )

  old = mod.method( :const_missing )
  mod.metaclass.class_eval do

    def autocreate( key, exemplar, &block )
      keys = case key
      when true, Symbol then [key]
      when Array then key
      end

      @exemplars ||= Hash.new
      @init_blocks ||= Hash.new { |h,k| h[k] = [] }
      keys.each do |k|
        @exemplars[k] = exemplar
        @init_blocks[k] << block
      end

      return self
    end

    def autocreate_class( key = true, superclass = Class )
      autocreate key, Class.new( superclass )
    end

    def autocreate_module( key = true )
      autocreate key, Module.new
    end

    def autoinit( key, &block )
      # See whether we're dealing with a namespaced constant,
      # The match groupings for,  e.g. "X::Y::Z", would be
      # ["X::Y", "Z"]
      match = key.to_s.match(/^(.*)::([\w\d_]+)$/)
      if match
        namespace, cname = match[1,2]
        const = module_eval(namespace)
        const.module_eval do
          @init_blocks ||= Hash.new { |h,k| h[k] = [] }
          @init_blocks[cname.to_sym] << block
        end
      else
        @init_blocks[key] << block
      end
      return self
    end

    def autoload(key = true, options = {})
      snake_case = lambda {|name| name.gsub(/([a-z\d])([A-Z])/){"#{$1}_#{$2}"}.tr("-", "_").downcase }
      # look for load_files in either a specified directory, or in the directory
      # with the snakecase name of the enclosing module
      directories = [options[:directories] || snake_case.call(self.name.match( /^.*::([\w\d_]+)$/)[1])].flatten
      # create a lambda that looks for a file to load
      file_finder = lambda do |cname|
        filename = snake_case.call(cname.to_s << ".rb")
        path = directories.map { |dir| File.join(dir.to_s, filename) }.find { |path| File.exist?( path ) }
      end
      # if no exemplar is given, assume Module.new
      @load_files ||= Hash.new
      @load_files[key] = [file_finder, options[:exemplar] || Module.new]
      return self
    end

    def autoload_class(key = true, superclass = Class, options = {})
      options[:exemplar] = Class.new(superclass)
      autoload key, options
    end

    def autoload_module(key = true, options = {})
      options[:exemplar] = Module.new
      autoload key, options
    end

    # Returns the list of constants that would be reloaded upon a call to reload.
    def reloadable( *names )
      ( @reloadable ||= [] ).concat(names)
      return self
    end

    # Reloads all the constants that were loaded via autocode. Technically, all reload
    # is doing is undefining them (by calling +remove_const+ on each in turn); they won't get
    # reloaded until they are referenced.
    def reload
      @reloadable.each { |name| remove_const( name ) } if @reloadable
      @reloadable = nil
      return self
    end

    # Unloads all the constants that were loaded and removes all auto* definitions.
    def unload
      reload
      @exemplars = @init_blocks = @load_files = nil
      return self
    end

    private

    define_method :const_missing do | cname | #:nodoc:
      cname = cname.to_sym
      @exemplars ||= Hash.new
      @init_blocks ||= Hash.new { |h,k| h[k] = [] }
      @load_files ||= Hash.new
      exemplar = @exemplars[cname] || @exemplars[true]
      blocks = @init_blocks[cname]
      blocks = @init_blocks[true] + blocks if @exemplars[cname].nil? && @init_blocks[true]
      load_file_finder, load_class = @load_files[cname] || @load_files[true]

      if load_file_finder && filename = load_file_finder.call(cname)
        object = load_class.clone
      elsif exemplar
        object = exemplar.clone
      else
        return old.call(cname)
      end

      (@reloadable ||= []) << cname;
      const_set( cname, object )

      blocks.each do |block|
        object.module_eval( &block) if block
      end
      
      load(filename) if filename
      
      return object
    end
  end
end