Module: HamlLint::Utils

Defined in:
lib/haml_lint/utils.rb

Overview

A miscellaneous set of utility functions.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.any_glob_matches?(globs_or_glob, file) ⇒ Boolean

Returns whether a glob pattern (or any of a list of patterns) matches the specified file.

This is defined here so our file globbing options are consistent everywhere we perform globbing.

Parameters:

  • glob (String, Array)
  • file (String)

Returns:

  • (Boolean)


19
20
21
22
23
24
25
26
27
# File 'lib/haml_lint/utils.rb', line 19

def any_glob_matches?(globs_or_glob, file)
  get_abs_and_rel_path(file).any? do |path|
    Array(globs_or_glob).any? do |glob|
      ::File.fnmatch?(glob, path,
                      ::File::FNM_PATHNAME | # Wildcards don't match path separators
                      ::File::FNM_DOTMATCH)  # `*` wildcard matches dotfiles
    end
  end
end

.camel_case(str) ⇒ String

Converts a string containing underscores/hyphens/spaces into CamelCase.

Parameters:

  • str (String)

Returns:

  • (String)


136
137
138
# File 'lib/haml_lint/utils.rb', line 136

def camel_case(str)
  str.split(/_|-| /).map { |part| part.sub(/^\w/, &:upcase) }.join
end

.check_error_when_compiling_haml(haml_string) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
# File 'lib/haml_lint/utils.rb', line 240

def check_error_when_compiling_haml(haml_string)
  begin
    ruby_code = ::HamlLint::Adapter.detect_class.new(haml_string).precompile
  rescue StandardError => e
    return e
  end
  eval("BEGIN {return nil}; #{ruby_code}", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
  # The eval will return nil
rescue ::SyntaxError
  $!
end

.count_consecutive(items, offset = 0) {|item| ... } ⇒ Integer

Count the number of consecutive items satisfying the given Proc.

Parameters:

  • items (Array)
  • offset (Fixnum) (defaults to: 0)

    index to start searching from

Yields:

  • (item)

    Passes item to the provided block.

Yield Parameters:

  • item (Object)

    Item to evaluate as matching criteria for inclusion

Yield Returns:

  • (Boolean)

    whether to include the item

Returns:

  • (Integer)


177
178
179
180
181
# File 'lib/haml_lint/utils.rb', line 177

def count_consecutive(items, offset = 0)
  count = 1
  count += 1 while (offset + count < items.count) && yield(items[offset + count])
  count
end

.extract_interpolated_values(text) {|interpolated_code, line| ... } ⇒ Object

Yields interpolated values within a block of text.

Parameters:

  • text (String)

Yields:

  • Passes interpolated code and line number that code appears on in the text.

Yield Parameters:

  • interpolated_code (String)

    code that was interpolated

  • line (Integer)

    line number code appears on in text



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

def extract_interpolated_values(text) # rubocop:disable Metrics/AbcSize
  dumped_text = text.dump

  # Basically, match pairs of '\' and '\ followed by the letter 'n'
  quoted_regex_s = "(#{Regexp.quote('\\\\')}|#{Regexp.quote('\\n')})"
  newline_positions = extract_substring_positions(dumped_text, quoted_regex_s)

  # Filter the matches to only keep those ending in 'n'.
  # This way, escaped \n will not be considered
  newline_positions.select! do |pos|
    dumped_text[pos - 1] == 'n'
  end

  Haml::Util.handle_interpolation(dumped_text) do |scan|
    line = (newline_positions.find_index { |marker| scan.charpos <= marker } ||
            newline_positions.size) + 1

    escape_count = (scan[2].size - 1) / 2
    break unless escape_count.even?

    dumped_interpolated_str = Haml::Util.balance(scan, '{', '}', 1)[0][0...-1]

    # Hacky way to turn a dumped string back into a regular string
    yield [eval('"' + dumped_interpolated_str + '"'), line] # rubocop:disable Security/Eval
  end
end

.extract_substring_positions(text, substr) ⇒ Array<Integer>

Returns indexes of all occurrences of a substring within a string.

Note, this will not return overlaping substrings, so searching for “aa” in “aaa” will only find one substring, not two.

Parameters:

  • text (String)

    the text to search

  • substr (String)

    the substring to search for

Returns:

  • (Array<Integer>)

    list of indexes where the substring occurs



125
126
127
128
129
130
# File 'lib/haml_lint/utils.rb', line 125

def extract_substring_positions(text, substr)
  positions = []
  scanner = StringScanner.new(text)
  positions << scanner.charpos while scanner.scan(/(.*?)#{substr}/)
  positions
end

.for_consecutive_items(items, satisfies, min_consecutive = 2) {|group| ... } ⇒ Object

Find all consecutive items satisfying the given block of a minimum size, yielding each group of consecutive items to the provided block.

Parameters:

  • items (Array)
  • satisfies (Proc)

    function that takes an item and returns true/false

  • min_consecutive (Fixnum) (defaults to: 2)

    minimum number of consecutive items before yielding the group

Yields:

  • Passes list of consecutive items all matching the criteria defined by the ‘satisfies` Proc to the provided block

Yield Parameters:

  • group (Array)

    List of consecutive items

Yield Returns:

  • (Boolean)

    block should return whether item matches criteria for inclusion



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/haml_lint/utils.rb', line 152

def for_consecutive_items(items, satisfies, min_consecutive = 2)
  current_index = -1

  while (current_index += 1) < items.count
    next unless satisfies[items[current_index]]

    count = count_consecutive(items, current_index, &satisfies)
    next unless count >= min_consecutive

    # Yield the chunk of consecutive items
    yield items[current_index...(current_index + count)]

    current_index += count # Skip this patch of consecutive items to find more
  end
end

.get_abs_and_rel_path(path) ⇒ Array<String>

Returns an array of two items, the first being the absolute path, the second the relative path.

The relative path is relative to the current working dir. The path passed can be either relative or absolute.

Parameters:

  • path (String)

    Path to get absolute and relative path of

Returns:

  • (Array<String>)

    Absolute and relative path



37
38
39
40
41
42
43
44
45
46
# File 'lib/haml_lint/utils.rb', line 37

def get_abs_and_rel_path(path)
  original_path = Pathname.new(path)
  root_dir_path = Pathname.new(File.expand_path(Dir.pwd))

  if original_path.absolute?
    [path, original_path.relative_path_from(root_dir_path)]
  else
    [root_dir_path + original_path, path]
  end
end

.handle_interpolation_with_indexes(text) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/haml_lint/utils.rb', line 82

def handle_interpolation_with_indexes(text)
  newline_indexes = extract_substring_positions(text, "\n")

  handle_interpolation_with_newline(text) do |scan|
    line_index = newline_indexes.find_index { |index| scan.charpos <= index }
    line_index ||= newline_indexes.size

    line_start_char_index = if line_index == 0
                              0
                            else
                              newline_indexes[line_index - 1]
                            end

    char_index = scan.charpos - line_start_char_index

    yield scan, line_index, char_index
  end
end

.indent(string, nb_indent) ⇒ Object



215
216
217
218
219
220
221
# File 'lib/haml_lint/utils.rb', line 215

def indent(string, nb_indent)
  if nb_indent < 0
    string.gsub(/^ {1,#{-nb_indent}}/, '')
  else
    string.gsub(/^/, ' ' * nb_indent)
  end
end

.insert_after_indentation(code, insert) ⇒ Object



194
195
196
197
# File 'lib/haml_lint/utils.rb', line 194

def insert_after_indentation(code, insert)
  index = code.index(/\S/)
  "#{code[0...index]}#{insert}#{code[index..]}"
end

.is_blank_line?(line) ⇒ Boolean

Returns true if line is only whitespace. Note, this is not like blank? is rails. For nil, this returns false.

Returns:

  • (Boolean)


236
237
238
# File 'lib/haml_lint/utils.rb', line 236

def is_blank_line?(line)
  line && line.index(/\S/).nil?
end

.map_after_first!(array, &block) ⇒ Object



230
231
232
# File 'lib/haml_lint/utils.rb', line 230

def map_after_first!(array, &block)
  map_subset!(array, 1..-1, &block)
end

.map_subset!(array, range, &block) ⇒ Object



223
224
225
226
227
228
# File 'lib/haml_lint/utils.rb', line 223

def map_subset!(array, range, &block)
  subset = array[range]
  return if subset.nil? || subset.empty?

  array[range] = subset.map(&block)
end

.process_erb(content) ⇒ String

Process ERB, providing some values for for versions to it

Parameters:

  • content (String)

    the (usually yaml) content to process

Returns:

  • (String)


187
188
189
190
191
192
# File 'lib/haml_lint/utils.rb', line 187

def process_erb(content)
  # Variables for use in the ERB's post-processing
  rubocop_version = HamlLint::VersionComparer.for_rubocop

  ERB.new(content).result(binding)
end

.regexp_for_parts(parts, join_regexp, prefix: nil, suffix: nil) ⇒ Object



280
281
282
283
284
# File 'lib/haml_lint/utils.rb', line 280

def regexp_for_parts(parts, join_regexp, prefix: nil, suffix: nil)
  regexp_code = parts.map { |c| Regexp.quote(c) }.join(join_regexp)
  regexp_code = "#{prefix}#{regexp_code}#{suffix}"
  Regexp.new(regexp_code)
end

.with_captured_streams(stdin_str, &_block) ⇒ String

Overrides the global stdin, stdout and stderr while within the block, to push a string in stdin, and capture both stdout and stderr which are returned.

Parameters:

  • stdin_str (String)

    the string to push in as stdin

  • _block (Block)

    the block to perform with the overridden std streams

Returns:

  • (String, String)


258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/haml_lint/utils.rb', line 258

def with_captured_streams(stdin_str, &_block)
  original_stdin = $stdin
  # The dup is needed so that stdin_data isn't altered (encoding-wise at least)
  $stdin = StringIO.new(stdin_str.dup)
  begin
    original_stdout = $stdout
    $stdout = StringIO.new
    begin
      original_stderr = $stderr
      $stderr = StringIO.new
      yield
      [$stdout.string, $stderr.string]
    ensure
      $stderr = original_stderr
    end
  ensure
    $stdout = original_stdout
  end
ensure
  $stdin = original_stdin
end

.with_environment(env) ⇒ Object

Calls a block of code with a modified set of environment variables, restoring them once the code has executed.

Parameters:

  • env (Hash)

    environment variables to set



203
204
205
206
207
208
209
210
211
212
213
# File 'lib/haml_lint/utils.rb', line 203

def with_environment(env)
  old_env = {}
  env.each do |var, value|
    old_env[var] = ENV[var.to_s]
    ENV[var.to_s] = value
  end

  yield
ensure
  old_env.each { |var, value| ENV[var.to_s] = value }
end

Instance Method Details

#handle_interpolation_with_newline(str) ⇒ Object

Same as Haml::Util.handle_interpolation, but enables multiline mode on the regex



103
104
105
106
107
# File 'lib/haml_lint/utils.rb', line 103

def handle_interpolation_with_newline(str)
  scan = StringScanner.new(str)
  yield scan while scan.scan(/(.*?)(\\*)#([{@$])/m)
  scan.rest
end