Module: Utils

Defined in:
extend/os/mac/utils/bottles.rb,
extend/os/mac/utils/analytics.rb,
utils.rb,
utils/ast.rb,
utils/git.rb,
utils/svn.rb,
utils/tar.rb,
utils/curl.rb,
utils/fork.rb,
utils/gzip.rb,
utils/link.rb,
utils/popen.rb,
utils/shell.rb,
utils/bottles.rb,
utils/shebang.rb,
utils/analytics.rb,
utils/inreplace.rb,
utils/autoremove.rb,
utils/git_repository.rb,
utils/topological_hash.rb

Defined Under Namespace

Modules: AST, Analytics, Bottles, Curl, Git, Gzip, Inreplace, Link, Shebang, Shell, Svn, Tar Classes: TopologicalHash

Class Method Summary collapse

Class Method Details

.deconstantize(path) ⇒ String

Removes the rightmost segment from the constant expression in the string.

deconstantize(‘Net::HTTP’) # => “Net” deconstantize(‘::Net::HTTP’) # => “::Net” deconstantize(‘String’) # => “” deconstantize(‘::String’) # => “” deconstantize(‘’) # => “”

See also #demodulize.

Parameters:

Returns:

See Also:



104
105
106
# File 'utils.rb', line 104

def self.deconstantize(path)
  T.must(path[0, path.rindex("::") || 0]) # implementation based on the one in facets' Module#spacename
end

.demodulize(path) ⇒ String

Removes the module part from the expression in the string.

demodulize(‘ActiveSupport::Inflector::Inflections’) # => “Inflections” demodulize(‘Inflections’) # => “Inflections” demodulize(‘::Inflections’) # => “Inflections” demodulize(‘’) # => “”

See also #deconstantize.

Parameters:

Returns:

See Also:



119
120
121
122
123
124
125
# File 'utils.rb', line 119

def self.demodulize(path)
  if (i = path.rindex("::"))
    T.must(path[(i + 2)..])
  else
    path
  end
end

.git_branch(repo = Pathname.pwd, safe: true) ⇒ String?

Gets the name of the currently checked-out branch, or HEAD if the repository is in a detached HEAD state.

Parameters:

  • repo (String, Pathname) (defaults to: Pathname.pwd)
  • safe (Boolean) (defaults to: true)

Returns:



42
43
44
45
# File 'utils/git_repository.rb', line 42

def self.git_branch(repo = Pathname.pwd, safe: true)
  repo = Pathname(repo).extend(GitRepositoryExtension)
  repo.git_branch(safe: safe)
end

.git_commit_message(repo = Pathname.pwd, commit: "HEAD", safe: true) ⇒ String?

Gets the full commit message of the specified commit, or of the HEAD commit if unspecified.

Parameters:

  • repo (String, Pathname) (defaults to: Pathname.pwd)
  • commit (String) (defaults to: "HEAD")
  • safe (Boolean) (defaults to: true)

Returns:



55
56
57
58
# File 'utils/git_repository.rb', line 55

def self.git_commit_message(repo = Pathname.pwd, commit: "HEAD", safe: true)
  repo = Pathname(repo).extend(GitRepositoryExtension)
  repo.git_commit_message(commit, safe: safe)
end

.git_head(repo = Pathname.pwd, length: nil, safe: true) ⇒ String?

Gets the full commit hash of the HEAD commit.

Parameters:

  • repo (String, Pathname) (defaults to: Pathname.pwd)
  • length (Integer, nil) (defaults to: nil)
  • safe (Boolean) (defaults to: true)

Returns:



15
16
17
18
19
20
# File 'utils/git_repository.rb', line 15

def self.git_head(repo = Pathname.pwd, length: nil, safe: true)
  return git_short_head(repo, length: length) if length.present?

  repo = Pathname(repo).extend(GitRepositoryExtension)
  repo.git_head(safe: safe)
end

.git_short_head(repo = Pathname.pwd, length: nil, safe: true) ⇒ String?

Gets a short commit hash of the HEAD commit.

Parameters:

  • repo (String, Pathname) (defaults to: Pathname.pwd)
  • length (Integer, nil) (defaults to: nil)
  • safe (Boolean) (defaults to: true)

Returns:



30
31
32
33
# File 'utils/git_repository.rb', line 30

def self.git_short_head(repo = Pathname.pwd, length: nil, safe: true)
  repo = Pathname(repo).extend(GitRepositoryExtension)
  repo.git_short_head(length: length, safe: safe)
end

.parse_author!(author) ⇒ Hash

Parameters:

Returns:

  • (Hash)

Raises:



140
141
142
143
144
145
146
147
148
149
# File 'utils.rb', line 140

def self.parse_author!(author)
  match_data = /^(?<name>[^<]+?)[ \t]*<(?<email>[^>]+?)>$/.match(author)
  if match_data
    name = match_data[:name]
    email = match_data[:email]
  end
  raise UsageError, "Unable to parse name and email." if name.blank? && email.blank?

  { name: T.must(name), email: T.must(email) }
end

.pluralize(stem, count, plural: "s", singular: "", include_count: false) ⇒ String

A lightweight alternative to ActiveSupport::Inflector.pluralize: Combines stem with the singular or plural suffix based on count. Adds a prefix of the count value if include_count is set to true.

Parameters:

  • stem (String)
  • count (Integer)
  • plural (String) (defaults to: "s")
  • singular (String) (defaults to: "")
  • include_count (Boolean) (defaults to: false)

Returns:



133
134
135
136
137
# File 'utils.rb', line 133

def self.pluralize(stem, count, plural: "s", singular: "", include_count: false)
  prefix = include_count ? "#{count} " : ""
  suffix = (count == 1) ? singular : plural
  "#{prefix}#{stem}#{suffix}"
end

.popen(args, mode, options = {}) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'utils/popen.rb', line 46

def self.popen(args, mode, options = {})
  IO.popen("-", mode) do |pipe|
    if pipe
      return pipe.read unless block_given?

      yield pipe
    else
      options[:err] ||= "/dev/null" unless ENV["HOMEBREW_STDERR"]
      begin
        exec(*args, options)
      rescue Errno::ENOENT
        $stderr.puts "brew: command not found: #{args[0]}" unless options[:err] == :close
        exit! 127
      rescue SystemCallError
        $stderr.puts "brew: exec failed: #{args[0]}" unless options[:err] == :close
        exit! 1
      end
    end
  end
end

.popen_read(*args, safe: false, **options, &block) ⇒ Object



8
9
10
11
12
13
# File 'utils/popen.rb', line 8

def self.popen_read(*args, safe: false, **options, &block)
  output = popen(args, "rb", options, &block)
  return output if !safe || $CHILD_STATUS.success?

  raise ErrorDuringExecution.new(args, status: $CHILD_STATUS, output: [[:stdout, output]])
end

.popen_write(*args, safe: false, **options) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'utils/popen.rb', line 19

def self.popen_write(*args, safe: false, **options)
  output = ""
  popen(args, "w+b", options) do |pipe|
    # Before we yield to the block, capture as much output as we can
    loop do
      output += pipe.read_nonblock(IO_DEFAULT_BUFFER_SIZE)
    rescue IO::WaitReadable, EOFError
      break
    end

    yield pipe
    pipe.close_write
    pipe.wait_readable

    # Capture the rest of the output
    output += pipe.read
    output.freeze
  end
  return output if !safe || $CHILD_STATUS.success?

  raise ErrorDuringExecution.new(args, status: $CHILD_STATUS, output: [[:stdout, output]])
end

.rewrite_child_error(child_error) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'utils/fork.rb', line 8

def self.rewrite_child_error(child_error)
  error = if child_error.inner["cmd"] &&
             child_error.inner_class == ErrorDuringExecution
    ErrorDuringExecution.new(child_error.inner["cmd"],
                             status: child_error.inner["status"],
                             output: child_error.inner["output"])
  elsif child_error.inner["cmd"] &&
        child_error.inner_class == BuildError
    # We fill `BuildError#formula` and `BuildError#options` in later,
    # when we rescue this in `FormulaInstaller#build`.
    BuildError.new(nil, child_error.inner["cmd"],
                   child_error.inner["args"], child_error.inner["env"])
  elsif child_error.inner_class == Interrupt
    Interrupt.new
  else
    # Everything other error in the child just becomes a RuntimeError.
    RuntimeError.new(child_error.message)
  end

  error.set_backtrace child_error.backtrace

  error
end

.safe_forkObject



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
# File 'utils/fork.rb', line 32

def self.safe_fork
  Dir.mktmpdir("homebrew", HOMEBREW_TEMP) do |tmpdir|
    UNIXServer.open("#{tmpdir}/socket") do |server|
      read, write = IO.pipe

      pid = fork do
        # bootsnap doesn't like these forked processes
        ENV["HOMEBREW_NO_BOOTSNAP"] = "1"

        ENV["HOMEBREW_ERROR_PIPE"] = server.path
        server.close
        read.close
        write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
        yield
      rescue Exception => e # rubocop:disable Lint/RescueException
        error_hash = JSON.parse e.to_json

        # Special case: We need to recreate ErrorDuringExecutions
        # for proper error messages and because other code expects
        # to rescue them further down.
        if e.is_a?(ErrorDuringExecution)
          error_hash["cmd"] = e.cmd
          error_hash["status"] = if e.status.is_a?(Process::Status)
            {
              exitstatus: e.status.exitstatus,
              termsig:    e.status.termsig,
            }
          else
            e.status
          end
          error_hash["output"] = e.output
        end

        write.puts error_hash.to_json
        write.close

        exit!
      else
        exit!(true)
      end

      ignore_interrupts(:quietly) do # the child will receive the interrupt and marshal it back
        begin
          socket = server.accept_nonblock
        rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
          retry unless Process.waitpid(T.must(pid), Process::WNOHANG)
        else
          socket.send_io(write)
          socket.close
        end
        write.close
        data = read.read
        read.close
        Process.wait(T.must(pid)) unless socket.nil?

        # 130 is the exit status for a process interrupted via Ctrl-C.
        # We handle it here because of the possibility of an interrupted process terminating
        # without writing its Interrupt exception to the error pipe.
        raise Interrupt if $CHILD_STATUS.exitstatus == 130

        if data.present?
          error_hash = JSON.parse(T.must(data.lines.first))

          e = ChildProcessError.new(error_hash)

          raise rewrite_child_error(e)
        end

        raise "Forked child process failed: #{$CHILD_STATUS}" unless $CHILD_STATUS.success?
      end
    end
  end
end

.safe_popen_read(*args, **options, &block) ⇒ Object



15
16
17
# File 'utils/popen.rb', line 15

def self.safe_popen_read(*args, **options, &block)
  popen_read(*args, safe: true, **options, &block)
end

.safe_popen_write(*args, **options, &block) ⇒ Object



42
43
44
# File 'utils/popen.rb', line 42

def self.safe_popen_write(*args, **options, &block)
  popen_write(*args, safe: true, **options, &block)
end

.underscore(camel_cased_word) ⇒ String

Makes an underscored, lowercase form from the expression in the string.

Changes ‘::’ to ‘/’ to convert namespaces to paths.

underscore(‘ActiveModel’) # => “active_model” underscore(‘ActiveModel::Errors’) # => “active_model/errors”

Parameters:

Returns:

See Also:



161
162
163
164
165
166
167
168
169
170
171
# File 'utils.rb', line 161

def self.underscore(camel_cased_word)
  return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)

  word = camel_cased_word.to_s.gsub("::", "/")
  word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
    T.must(::Regexp.last_match(1) || ::Regexp.last_match(2)) << "_"
  end
  word.tr!("-", "_")
  word.downcase!
  word
end