Class: Rack::Unreloader::Reloader

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/unreloader/reloader.rb

Direct Known Subclasses

AutoloadReloader

Constant Summary collapse

File =
::File

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Reloader

Setup the reloader. Supports :logger and :subclasses options, see Rack::Unloader.new for details.



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
# File 'lib/rack/unreloader/reloader.rb', line 10

def initialize(opts={})
  @logger = opts[:logger]
  @classes = opts[:subclasses] ?  Array(opts[:subclasses]).map(&:to_s) : %w'Object'

  # Hash of files being monitored for changes, keyed by absolute path of file name,
  # with values being an array containing the last modified time (or nil if the file has
  # not yet been loaded) and the delete hook.
  @monitor_files = {}

  # Hash of directories being monitored for changes, keyed by absolute path of directory name,
  # with values being the an array with a hash of modified times for the directory and
  # subdirectories (or nil if the directory has not yet been checked), an array of files in
  # the directory, a block to pass to require_dependency for new files, and the delete_hook
  # for the files in the directory.
  @monitor_dirs = {}

  # Hash of procs returning constants defined in files, keyed by absolute path
  # of file name.  If there is no proc, must call ObjectSpace before and after
  # loading files to detect changes, which is slower.
  @constants_defined = {}

  # Hash keyed by absolute path of file name, storing constants and other
  # filenames that the key loads.  Values should be hashes with :constants
  # and :features keys, and arrays of values.
  @files = {}

  # Similar to @files, but stores previous entries, used when rolling back.
  @old_entries = {}

  # Records dependencies on files.  Keys are absolute paths, values are arrays of absolute paths,
  # where each entry depends on the key, so that if the key path is modified, all values are
  # reloaded.
  @dependencies = {}

  # Array of the order in which to load dependencies
  @dependency_order = []

  # Array of absolute paths which should be unloaded, but not reloaded on changes,
  # because files that depend on them will load them automatically.
  @skip_reload = []
end

Instance Method Details

#clear!Object

Unload all reloadable constants and features, and clear the list of files to monitor.



54
55
56
57
58
59
60
# File 'lib/rack/unreloader/reloader.rb', line 54

def clear!
  @files.keys.each do |file|
    remove(file)
  end
  @monitor_files = {}
  @old_entries = {}
end

#record_dependency(path, files) ⇒ Object

Record a dependency the given files, such that each file in files depends on path. If path changes, each file in files should be reloaded as well.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rack/unreloader/reloader.rb', line 65

def record_dependency(path, files)
  files = (@dependencies[path] ||= []).concat(files)
  files.uniq!

  order = @dependency_order
  i = order.find_index{|v| files.include?(v)} || -1
  order.insert(i, path)
  order.concat(files)
  order.uniq!

  if File.directory?(path)
    (@monitor_files.keys & Unreloader.ruby_files(path)).each do |file|
      record_dependency(file, files)
    end
  end

  nil
end

#reload!Object

If there are any changed files, reload them. If there are no changed files, do nothing.



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
# File 'lib/rack/unreloader/reloader.rb', line 86

def reload!
  changed_files = []

  @monitor_dirs.keys.each do |dir|
    check_monitor_dir(dir, changed_files)
  end

  removed_files = []
  delete_hooks = []

  @monitor_files.to_a.each do |file, (time, delete_hook)|
    if File.file?(file)
      if file_changed?(file, time)
        changed_files << file
      end
    else
      delete_hooks << [delete_hook, file] if delete_hook
      removed_files << file
    end
  end

  remove_files(removed_files)
  delete_hooks.each{|hook, file| hook.call(file)}

  return if changed_files.empty?

  unless @dependencies.empty?
    changed_files = Unreloader.expand_directory_paths(reload_files(changed_files))
    
    order = @dependency_order
    order &= changed_files
    changed_files = order + (changed_files - order)
  end

  unless @skip_reload.empty?
    changed_files -= Unreloader.expand_directory_paths(@skip_reload)
  end

  changed_files.select! do |file|
    @monitor_files.has_key?(file)
  end
  changed_files.each do |file|
    safe_load(file)
  end
end

#require_dependencies(paths, opts = {}, &block) ⇒ Object

Require the given dependencies, monitoring them for changes. Paths should be a file glob or an array of file globs.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rack/unreloader/reloader.rb', line 134

def require_dependencies(paths, opts={}, &block)
  options = {:cyclic => true}
  delete_hook = opts[:delete_hook]
  error = nil 

  Unreloader.expand_paths(paths).each do |file|
    if File.directory?(file)
      @monitor_dirs[file] = [nil, [], block, delete_hook]
      check_monitor_dir(file)
      next
    else
      @constants_defined[file] = block
      @monitor_files[file] = [nil, delete_hook]
    end

    begin
      safe_load(file, options)
    rescue NameError, LoadError => error
      log "Cyclic dependency reload for #{error.class}: #{error.message}"
    rescue Exception => error
      log "Error: #{error.class}: #{error.message}"
      break
    end
  end

  if error
    raise error
  end
end

#skip_reload(files) ⇒ Object

Skip reloading the given files. Should only be used if other files depend on these files and the other files require these files when loaded.



167
168
169
170
171
# File 'lib/rack/unreloader/reloader.rb', line 167

def skip_reload(files)
  @skip_reload.concat(files)
  @skip_reload.uniq!
  nil
end