Class: RubyLint::FileScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-lint/file_scanner.rb

Overview

FileScanner is used for finding a list of files that could potentially define a given Ruby constant (path).

Constant Summary

RUBY_DIRECTORIES =

Array containing names of directories that (often) contain Ruby source files.

Returns:

  • (Array)
%w{app lib}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(directories = self.class.default_directories, ignore = []) ⇒ FileScanner

Returns a new instance of FileScanner

Parameters:

  • directories (Array) (defaults to: self.class.default_directories)

    A collection of base directories to search in.

  • ignore (Array) (defaults to: [])

    A list of paths to ignore.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ruby-lint/file_scanner.rb', line 42

def initialize(directories = self.class.default_directories, ignore = [])
  unless directories.respond_to?(:each)
    raise TypeError, 'Directories must be specified as an Enumerable'
  end

  @directories = directories
  @ignore      = ignore || []

  # Hash that will contain the matching file paths for a given constant.
  @constant_paths_cache = {}
end

Instance Attribute Details

#directoriesArray (readonly)

Returns:

  • (Array)


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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ruby-lint/file_scanner.rb', line 12

class FileScanner
  attr_reader :directories, :ignore

  ##
  # Array containing names of directories that (often) contain Ruby source
  # files.
  #
  # @return [Array]
  #
  RUBY_DIRECTORIES = %w{app lib}

  ##
  # @return [Array]
  #
  def self.default_directories
    directories = []

    RUBY_DIRECTORIES.each do |dir|
      path = File.join(Dir.pwd, dir)

      directories << path if File.directory?(path)
    end

    return directories
  end

  ##
  # @param [Array] directories A collection of base directories to search in.
  # @param [Array] ignore A list of paths to ignore.
  #
  def initialize(directories = self.class.default_directories, ignore = [])
    unless directories.respond_to?(:each)
      raise TypeError, 'Directories must be specified as an Enumerable'
    end

    @directories = directories
    @ignore      = ignore || []

    # Hash that will contain the matching file paths for a given constant.
    @constant_paths_cache = {}
  end

  ##
  # Tries to find `constant` in one of the directories. The return value is
  # an Array of file paths sorted from top-level to deeply nested structures
  # (e.g. `a.rb` comes before `foo/a.rb`).
  #
  # @param [String] constant
  # @return [Array]
  #
  def scan(constant)
    unless constant_paths_cached?(constant)
      build_constant_paths_cache(constant)
    end

    return @constant_paths_cache[constant]
  end

  ##
  # @return [Array]
  #
  def glob_cache
    @glob_cache ||= directories.empty? ? [] : glob_ruby_files
  end

  ##
  # @return [Array]
  #
  def glob_ruby_files
    return Dir.glob("{#{directories.join(',')}}/**/*.rb")
  end

  ##
  # Returns the file path for the given constant.
  #
  # @example
  #  constant_to_path('FooBar::Baz') # => "foo_bar/baz.rb"
  #
  # @param [String] constant
  # @return [String]
  #
  def constant_to_path(constant)
    return constant.gsub('::', '/').snake_case + '.rb'
  end

  ##
  # Returns a path similar to {#constant_to_path} but using dashes instead of
  # underscores for the first directory.
  #
  # @example
  #  constant_to_dashed_path('FooBar::Baz') # => "foo-bar/baz.rb"
  #
  # @see [#constant_to_path]
  #
  def constant_to_dashed_path(constant)
    const_segments = constant.split('::')
    path_segments  = []

    const_segments.each_with_index do |segment, index|
      segment = segment.snake_case

      # Use dashes for the first segment (= top level directory).
      if const_segments.length > 1 and index == 0
        segment = segment.gsub('_', '-')
      end

      path_segments << segment
    end

    return path_segments.join('/') + '.rb'
  end

  ##
  # Searches all the files that could potentially define the given constant
  # and caches them.
  #
  # @param [String] constant
  #
  def build_constant_paths_cache(constant)
    paths = match_globbed_files(constant_to_path(constant))

    # Lets see if we can find anything when using dashes for the directory
    # names instead of underscores.
    if paths.empty?
      paths = match_globbed_files(constant_to_dashed_path(constant))
    end

    paths.map! { |p| File.expand_path(p) }

    ignore.each do |pattern|
      paths.reject! do |path|
        path.include?(pattern)
      end
    end

    # Ensure that the order is from top-level -> deeply nested files
    # instead of a random order.
    paths.sort! do |left, right|
      left.length <=> right.length
    end

    @constant_paths_cache[constant] = paths
  end

  ##
  # @return [Array]
  #
  def match_globbed_files(segment)
    # Ensure that we match entire path segments. Just using the segment would
    # result in partial filename matching (e.g. "foo.rb" matching
    # "bar_foo.rb"). We don't want that.
    segment = "/#{segment}"

    return glob_cache.select { |p| p.include?(segment) }
  end

  ##
  # @return [TrueClass|FalseClass]
  #
  def constant_paths_cached?(constant)
    return @constant_paths_cache.key?(constant)
  end
end

#ignoreArray (readonly)

Returns:

  • (Array)


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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ruby-lint/file_scanner.rb', line 12

class FileScanner
  attr_reader :directories, :ignore

  ##
  # Array containing names of directories that (often) contain Ruby source
  # files.
  #
  # @return [Array]
  #
  RUBY_DIRECTORIES = %w{app lib}

  ##
  # @return [Array]
  #
  def self.default_directories
    directories = []

    RUBY_DIRECTORIES.each do |dir|
      path = File.join(Dir.pwd, dir)

      directories << path if File.directory?(path)
    end

    return directories
  end

  ##
  # @param [Array] directories A collection of base directories to search in.
  # @param [Array] ignore A list of paths to ignore.
  #
  def initialize(directories = self.class.default_directories, ignore = [])
    unless directories.respond_to?(:each)
      raise TypeError, 'Directories must be specified as an Enumerable'
    end

    @directories = directories
    @ignore      = ignore || []

    # Hash that will contain the matching file paths for a given constant.
    @constant_paths_cache = {}
  end

  ##
  # Tries to find `constant` in one of the directories. The return value is
  # an Array of file paths sorted from top-level to deeply nested structures
  # (e.g. `a.rb` comes before `foo/a.rb`).
  #
  # @param [String] constant
  # @return [Array]
  #
  def scan(constant)
    unless constant_paths_cached?(constant)
      build_constant_paths_cache(constant)
    end

    return @constant_paths_cache[constant]
  end

  ##
  # @return [Array]
  #
  def glob_cache
    @glob_cache ||= directories.empty? ? [] : glob_ruby_files
  end

  ##
  # @return [Array]
  #
  def glob_ruby_files
    return Dir.glob("{#{directories.join(',')}}/**/*.rb")
  end

  ##
  # Returns the file path for the given constant.
  #
  # @example
  #  constant_to_path('FooBar::Baz') # => "foo_bar/baz.rb"
  #
  # @param [String] constant
  # @return [String]
  #
  def constant_to_path(constant)
    return constant.gsub('::', '/').snake_case + '.rb'
  end

  ##
  # Returns a path similar to {#constant_to_path} but using dashes instead of
  # underscores for the first directory.
  #
  # @example
  #  constant_to_dashed_path('FooBar::Baz') # => "foo-bar/baz.rb"
  #
  # @see [#constant_to_path]
  #
  def constant_to_dashed_path(constant)
    const_segments = constant.split('::')
    path_segments  = []

    const_segments.each_with_index do |segment, index|
      segment = segment.snake_case

      # Use dashes for the first segment (= top level directory).
      if const_segments.length > 1 and index == 0
        segment = segment.gsub('_', '-')
      end

      path_segments << segment
    end

    return path_segments.join('/') + '.rb'
  end

  ##
  # Searches all the files that could potentially define the given constant
  # and caches them.
  #
  # @param [String] constant
  #
  def build_constant_paths_cache(constant)
    paths = match_globbed_files(constant_to_path(constant))

    # Lets see if we can find anything when using dashes for the directory
    # names instead of underscores.
    if paths.empty?
      paths = match_globbed_files(constant_to_dashed_path(constant))
    end

    paths.map! { |p| File.expand_path(p) }

    ignore.each do |pattern|
      paths.reject! do |path|
        path.include?(pattern)
      end
    end

    # Ensure that the order is from top-level -> deeply nested files
    # instead of a random order.
    paths.sort! do |left, right|
      left.length <=> right.length
    end

    @constant_paths_cache[constant] = paths
  end

  ##
  # @return [Array]
  #
  def match_globbed_files(segment)
    # Ensure that we match entire path segments. Just using the segment would
    # result in partial filename matching (e.g. "foo.rb" matching
    # "bar_foo.rb"). We don't want that.
    segment = "/#{segment}"

    return glob_cache.select { |p| p.include?(segment) }
  end

  ##
  # @return [TrueClass|FalseClass]
  #
  def constant_paths_cached?(constant)
    return @constant_paths_cache.key?(constant)
  end
end

Class Method Details

.default_directoriesArray

Returns:

  • (Array)


26
27
28
29
30
31
32
33
34
35
36
# File 'lib/ruby-lint/file_scanner.rb', line 26

def self.default_directories
  directories = []

  RUBY_DIRECTORIES.each do |dir|
    path = File.join(Dir.pwd, dir)

    directories << path if File.directory?(path)
  end

  return directories
end

Instance Method Details

#build_constant_paths_cache(constant) ⇒ Object

Searches all the files that could potentially define the given constant and caches them.

Parameters:



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
# File 'lib/ruby-lint/file_scanner.rb', line 130

def build_constant_paths_cache(constant)
  paths = match_globbed_files(constant_to_path(constant))

  # Lets see if we can find anything when using dashes for the directory
  # names instead of underscores.
  if paths.empty?
    paths = match_globbed_files(constant_to_dashed_path(constant))
  end

  paths.map! { |p| File.expand_path(p) }

  ignore.each do |pattern|
    paths.reject! do |path|
      path.include?(pattern)
    end
  end

  # Ensure that the order is from top-level -> deeply nested files
  # instead of a random order.
  paths.sort! do |left, right|
    left.length <=> right.length
  end

  @constant_paths_cache[constant] = paths
end

#constant_paths_cached?(constant) ⇒ TrueClass|FalseClass

Returns:

  • (TrueClass|FalseClass)


171
172
173
# File 'lib/ruby-lint/file_scanner.rb', line 171

def constant_paths_cached?(constant)
  return @constant_paths_cache.key?(constant)
end

#constant_to_dashed_path(constant) ⇒ Object

Returns a path similar to #constant_to_path but using dashes instead of underscores for the first directory.

Examples:

constant_to_dashed_path('FooBar::Baz') # => "foo-bar/baz.rb"

See Also:

  • RubyLint::FileScanner.[[#constant_to_path]


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ruby-lint/file_scanner.rb', line 106

def constant_to_dashed_path(constant)
  const_segments = constant.split('::')
  path_segments  = []

  const_segments.each_with_index do |segment, index|
    segment = segment.snake_case

    # Use dashes for the first segment (= top level directory).
    if const_segments.length > 1 and index == 0
      segment = segment.gsub('_', '-')
    end

    path_segments << segment
  end

  return path_segments.join('/') + '.rb'
end

#constant_to_path(constant) ⇒ String

Returns the file path for the given constant.

Examples:

constant_to_path('FooBar::Baz') # => "foo_bar/baz.rb"

Parameters:

Returns:



93
94
95
# File 'lib/ruby-lint/file_scanner.rb', line 93

def constant_to_path(constant)
  return constant.gsub('::', '/').snake_case + '.rb'
end

#glob_cacheArray

Returns:

  • (Array)


73
74
75
# File 'lib/ruby-lint/file_scanner.rb', line 73

def glob_cache
  @glob_cache ||= directories.empty? ? [] : glob_ruby_files
end

#glob_ruby_filesArray

Returns:

  • (Array)


80
81
82
# File 'lib/ruby-lint/file_scanner.rb', line 80

def glob_ruby_files
  return Dir.glob("{#{directories.join(',')}}/**/*.rb")
end

#match_globbed_files(segment) ⇒ Array

Returns:

  • (Array)


159
160
161
162
163
164
165
166
# File 'lib/ruby-lint/file_scanner.rb', line 159

def match_globbed_files(segment)
  # Ensure that we match entire path segments. Just using the segment would
  # result in partial filename matching (e.g. "foo.rb" matching
  # "bar_foo.rb"). We don't want that.
  segment = "/#{segment}"

  return glob_cache.select { |p| p.include?(segment) }
end

#scan(constant) ⇒ Array

Tries to find constant in one of the directories. The return value is an Array of file paths sorted from top-level to deeply nested structures (e.g. a.rb comes before foo/a.rb).

Parameters:

Returns:

  • (Array)


62
63
64
65
66
67
68
# File 'lib/ruby-lint/file_scanner.rb', line 62

def scan(constant)
  unless constant_paths_cached?(constant)
    build_constant_paths_cache(constant)
  end

  return @constant_paths_cache[constant]
end