Module: Pathological

Defined in:
lib/pathological/base.rb

Defined Under Namespace

Classes: NoPathfileException, PathologicalException

Constant Summary

PATHFILE_NAME =
"Pathfile"

Class Method Summary (collapse)

Class Method Details

+ (Object) add_paths!(load_path = $LOAD_PATH, paths = nil)

Add paths to the load path.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/pathological/base.rb', line 13

def self.add_paths!(load_path = $LOAD_PATH, paths = nil)
  begin
    paths ||= find_load_paths
  rescue NoPathfileException
    STDERR.puts "Warning: using Pathological, but no Pathfile was found."
    return
  end
  paths.each do |path|
    if load_path.include? path
      debug "Skipping <#{path}>, which is already in the load path."
    else
      debug "Adding <#{path}> to load path."
      load_path << path
      @@loaded_paths << path
    end
  end
end

+ (Object) bundlerize_mode



136
137
138
139
140
141
142
143
144
# File 'lib/pathological/base.rb', line 136

def self.bundlerize_mode
  pathfile = self.find_pathfile
  raise NoPathfileException unless pathfile
  bundle_gemfile = File.join(File.dirname(pathfile), "Gemfile")
  unless File.file? bundle_gemfile
    raise PathologicalException, "No Gemfile found in #{File.dirname(pathfile)}."
  end
  ENV["BUNDLE_GEMFILE"] = bundle_gemfile
end

+ (Object) copy_outside_paths!(destination, options = {})

Copies directories in pathfile to a destination, such that the destination has no references to directories outside of the destination in the load path.

Hierarchy of destination directory:

destination/
   Pathfile     # new paths
   dependency_directory/
      dependency1         # Copied from original location

This is very useful for deployment, for example.

TODO(ev): Break this function up into a set of more functional primitives



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

def self.copy_outside_paths!(destination, options = {})
  options = { :dependency_directory => "pathological_dependencies" }.merge(options)
  saved_exclude_root = @@exclude_root
  begin
    self.excluderoot_mode
    pathfile = self.find_pathfile(options[:pathfile_search_path])
    # Nothing to do if there's no Pathfile
    return unless pathfile && File.file?(pathfile)

    foreign_paths = self.find_load_paths(pathfile).uniq
    return if foreign_paths.empty?

    path_root = File.join(destination, options[:dependency_directory])
    FileUtils.mkdir_p path_root

    # Copy in each path and save the relative paths to write to the rewritten Pathfile. We copy each unique
    # path into the folder not as the basename, but as the longest suffix of the path necessary to make it
    # unique. (Otherwise this won't work if you have two entries with the same basename in the Pathfile,
    # such as "foo/lib" and "bar/lib".)
    common_prefix = find_longest_common_prefix(foreign_paths)
    new_pathfile_paths = foreign_paths.map do |foreign_path|
      path_short_name = foreign_path.gsub(/^#{common_prefix}/, "")
      symlinked_name = File.join(path_root, path_short_name)
      FileUtils.mkdir_p File.split(symlinked_name)[0]
      debug "About to move #{foreign_path} to #{symlinked_name}..."
      copy_directory(foreign_path, symlinked_name)
      File.join(options[:dependency_directory], path_short_name)
    end
    # Overwrite the Pathfile with the new relative paths.
    File.open(File.join(destination, "Pathfile"), "w") do |file|
      new_pathfile_paths.each { |path| file.puts path }
    end
  ensure
    @@exclude_root = saved_exclude_root
  end
end

+ (Object) debug_mode

Convenience functions for the various modes in which Pathological may run.



135
# File 'lib/pathological/base.rb', line 135

def self.debug_mode; @@debug = true; end

+ (Object) excluderoot_mode



147
# File 'lib/pathological/base.rb', line 147

def self.excluderoot_mode; @@exclude_root = true; end

+ (Array<String>) find_load_paths(pathfile = nil)

For some pathfile, parse it and find all the load paths that it references.



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/pathological/base.rb', line 35

def self.find_load_paths(pathfile = nil)
  pathfile ||= find_pathfile
  raise NoPathfileException unless pathfile
  begin
    pathfile_handle = File.open(pathfile)
  rescue Errno::ENOENT
    raise NoPathfileException
  rescue
    raise PathologicalException, "There was an error opening the pathfile <#{pathfile}>."
  end
  parse_pathfile(pathfile_handle)
end

+ (String?) find_pathfile(directory = nil)

Find the pathfile by searching up from a starting directory. Symlinks are expanded out.



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

def self.find_pathfile(directory = nil)
  # If we're in IRB, use the working directory as the root of the search path for the Pathfile.
  if $0 != __FILE__ && $0 == "irb"
    directory = Dir.pwd
    debug "In IRB -- using the cwd (#{directory}) as the search root for Pathfile."
  end
  return nil if directory && !File.directory?(directory)
  # Find the full, absolute path of this directory, resolving symlinks. If no directory was given, use the
  # directory where the file requiring pathological resides.
  full_path = real_path(directory || requiring_filename)
  current_path = directory ? full_path : File.dirname(full_path)
  loop do
    debug "Searching <#{current_path}> for Pathfile."
    pathfile = File.join(current_path, PATHFILE_NAME)
    if File.file? pathfile
      debug "Pathfile found: <#{pathfile}>."
      return pathfile
    end
    new_path = File.dirname current_path
    if new_path == current_path
      debug "Reached filesystem root, but no Pathfile found."
      return nil
    end
    current_path = new_path
  end
end

+ (Object) noexceptions_mode



146
# File 'lib/pathological/base.rb', line 146

def self.noexceptions_mode; @@no_exceptions = true; end

+ (Object) parentdir_mode



145
# File 'lib/pathological/base.rb', line 145

def self.parentdir_mode; @@add_parents = true; end

+ (String) requiring_filename

Searches the call stack for the file that required pathological. If no file can be found, falls back to the currently executing file ($0). This handles the case where the app was launched by another executable (rake, thin, etc.)



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pathological/base.rb', line 243

def self.requiring_filename
  # Match paths like .../gems/pathological-0.2.2.1/lib/pathological/base.rb and also
  # .../gems/pathological-0.2.2.1/lib/pathological.rb
  pathological_file_pattern = %r{/pathological(/[^/]+|)\.rb}
  requiring_file = Kernel.caller.find do |stack_line|
    if RUBY_VERSION.start_with?("1.9")
      # In Ruby 1.9, top-level files will have the string "top (required)" included in the stack listing.
      stack_line.include?("top (required)") && stack_line !~ pathological_file_pattern
    else
      # In Ruby 1.8, top-level files are listed with their relative path and without a line number.
      stack_line !~ /:\d+:in/ && stack_line !~ pathological_file_pattern
    end
  end
  requiring_file ? requiring_file.match(/(.+):\d+/)[1] : $0 rescue $0
end

+ (Object) reset!

Reset all Pathological options (useful if you want to require a different Pathfile)



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/pathological/base.rb', line 150

def self.reset!
  # Debug mode -- print out information about load paths
  @@debug = false
  # Parentdir mode -- add unique parents of specified directories.
  @@add_parents = false
  # Noexceptions mode -- don't raise exceptions if the Pathfile contains bad paths
  @@no_exceptions = false
  # Excluderoot mode -- don't add the project root (where the Pathfile lives) to the load path
  @@exclude_root = false

  @@loaded_paths ||= []
  @@loaded_paths.each { |path| $LOAD_PATH.delete path }
  @@loaded_paths = []
end