Class: Pathname

Inherits:
Object
  • Object
show all
Includes:
DiskUsageExtension
Defined in:
Library/Homebrew/extend/pathname.rb,
Library/Homebrew/extend/os/mac/extend/pathname.rb,
Library/Homebrew/extend/os/linux/extend/pathname.rb

Overview

Homebrew extends Ruby's `Pathname` to make our code more readable.

Constant Summary

BOTTLE_EXTNAME_RX =
/(\.[a-z0-9_]+\.bottle\.(\d+\.)?tar\.gz)$/

Instance Method Summary collapse

Methods included from DiskUsageExtension

#abv, #disk_usage, #file_count

Instance Method Details

#/(other) ⇒ Object



369
370
371
372
373
374
# File 'Library/Homebrew/extend/pathname.rb', line 369

def /(other)
  if !other.respond_to?(:to_str) && !other.respond_to?(:to_path)
    odeprecated "Pathname#/ with #{other.class}", "a String or a Pathname"
  end
  join(other.to_s)
end

#append_lines(content, *open_args) ⇒ Object

Only appends to a file that is already created.



152
153
154
155
# File 'Library/Homebrew/extend/pathname.rb', line 152

def append_lines(content, *open_args)
  raise "Cannot append file that doesn't exist: #{self}" unless exist?
  open("a", *open_args) { |f| f.puts(content) }
end

#atomic_write(content) ⇒ Object

NOTE always overwrites



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'Library/Homebrew/extend/pathname.rb', line 170

def atomic_write(content)
  require "tempfile"
  tf = Tempfile.new(basename.to_s, dirname)
  begin
    tf.binmode
    tf.write(content)

    begin
      old_stat = stat
    rescue Errno::ENOENT
      old_stat = default_stat
    end

    uid = Process.uid
    gid = Process.groups.delete(old_stat.gid) { Process.gid }

    begin
      tf.chown(uid, gid)
      tf.chmod(old_stat.mode)
    rescue Errno::EPERM # rubocop:disable Lint/HandleExceptions
    end

    # Close the file before renaming to prevent the error: Device or resource busy
    # Affects primarily NFS.
    tf.close
    File.rename(tf.path, self)
  ensure
    tf.close!
  end
end

#binread(*open_args) ⇒ Object



164
165
166
# File 'Library/Homebrew/extend/pathname.rb', line 164

def binread(*open_args)
  open("rb", *open_args, &:read)
end

#binwrite(contents, *open_args) ⇒ Object



158
159
160
# File 'Library/Homebrew/extend/pathname.rb', line 158

def binwrite(contents, *open_args)
  open("wb", *open_args) { |f| f.write(contents) }
end

#cdObject



339
340
341
# File 'Library/Homebrew/extend/pathname.rb', line 339

def cd
  Dir.chdir(self) { yield self }
end

#compression_typeObject



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'Library/Homebrew/extend/pathname.rb', line 269

def compression_type
  case extname
  when ".jar", ".war"
    # Don't treat jars or wars as compressed
    return
  when ".gz"
    # If the filename ends with .gz not preceded by .tar
    # then we want to gunzip but not tar
    return :gzip_only
  when ".bz2"
    return :bzip2_only
  when ".lha", ".lzh"
    return :lha
  end

  # Get enough of the file to detect common file types
  # POSIX tar magic has a 257 byte offset
  # magic numbers stolen from /usr/share/file/magic/
  case open("rb") { |f| f.read(262) }
  when /^PK\003\004/n         then :zip
  when /^\037\213/n           then :gzip
  when /^BZh/n                then :bzip2
  when /^\037\235/n           then :compress
  when /^.{257}ustar/n        then :tar
  when /^\xFD7zXZ\x00/n       then :xz
  when /^LZIP/n               then :lzip
  when /^Rar!/n               then :rar
  when /^7z\xBC\xAF\x27\x1C/n then :p7zip
  when /^xar!/n               then :xar
  when /^\xed\xab\xee\xdb/n   then :rpm
  else
    # This code so that bad-tarballs and zips produce good error messages
    # when they don't unarchive properly.
    case extname
    when ".tar.gz", ".tgz", ".tar.bz2", ".tbz" then :tar
    when ".zip" then :zip
    end
  end
end

#cp_path_sub(pattern, replacement) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'Library/Homebrew/extend/pathname.rb', line 211

def cp_path_sub(pattern, replacement)
  raise "#{self} does not exist" unless exist?

  dst = sub(pattern, replacement)

  raise "#{self} is the same file as #{dst}" if self == dst

  if directory?
    dst.mkpath
  else
    dst.dirname.mkpath
    dst = yield(self, dst) if block_given?
    FileUtils.cp(self, dst)
  end
end

#ds_store?Boolean



464
465
466
# File 'Library/Homebrew/extend/pathname.rb', line 464

def ds_store?
  basename.to_s == ".DS_Store"
end

#ensure_writableObject



378
379
380
381
382
383
384
385
386
387
# File 'Library/Homebrew/extend/pathname.rb', line 378

def ensure_writable
  saved_perms = nil
  unless writable_real?
    saved_perms = stat.mode
    FileUtils.chmod "u+rw", to_path
  end
  yield
ensure
  chmod saved_perms if saved_perms
end

#env_script_all_files(dst, env) ⇒ Object

Writes a wrapper env script and moves all files to the dst



428
429
430
431
432
433
434
435
436
# File 'Library/Homebrew/extend/pathname.rb', line 428

def env_script_all_files(dst, env)
  dst.mkpath
  Pathname.glob("#{self}/*") do |file|
    next if file.directory?
    dst.install(file)
    new_file = dst.join(file.basename)
    file.write_env_script(new_file, env)
  end
end

#extname(path = to_s) ⇒ Object

extended to support common double extensions



231
232
233
234
235
236
237
# File 'Library/Homebrew/extend/pathname.rb', line 231

def extname(path = to_s)
  bottle_ext = path[BOTTLE_EXTNAME_RX, 1]
  return bottle_ext if bottle_ext
  archive_ext = path[/(\.(tar|cpio|pax)\.(gz|bz2|lz|xz|Z))$/, 1]
  return archive_ext if archive_ext
  File.extname(path)
end

#extname_oldObject



228
# File 'Library/Homebrew/extend/pathname.rb', line 228

alias extname_old extname

#incremental_hash(klass) ⇒ Object



315
316
317
318
319
320
321
322
323
324
# File 'Library/Homebrew/extend/pathname.rb', line 315

def incremental_hash(klass)
  digest = klass.new
  if digest.respond_to?(:file)
    digest.file(self)
  else
    buf = ""
    open("rb") { |f| digest << buf while f.read(16384, buf) }
  end
  digest.hexdigest
end

#install(*sources) ⇒ Object

Moves a file from the original location to the Pathname's.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'Library/Homebrew/extend/pathname.rb', line 70

def install(*sources)
  sources.each do |src|
    case src
    when Resource
      src.stage(self)
    when Resource::Partial
      src.resource.stage { install(*src.files) }
    when Array
      if src.empty?
        opoo "tried to install empty array to #{self}"
        break
      end
      src.each { |s| install_p(s, File.basename(s)) }
    when Hash
      if src.empty?
        opoo "tried to install empty hash to #{self}"
        break
      end
      src.each { |s, new_basename| install_p(s, new_basename) }
    else
      install_p(src, File.basename(src))
    end
  end
end

#install_infoObject



390
391
392
# File 'Library/Homebrew/extend/pathname.rb', line 390

def install_info
  quiet_system "/usr/bin/install-info", "--quiet", to_s, "#{dirname}/dir"
end

#install_metafiles(from = Pathname.pwd) ⇒ Object



450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'Library/Homebrew/extend/pathname.rb', line 450

def install_metafiles(from = Pathname.pwd)
  Pathname(from).children.each do |p|
    next if p.directory?
    next unless Metafiles.copy?(p.basename.to_s)
    # Some software symlinks these files (see help2man.rb)
    filename = p.resolved_path
    # Some software links metafiles together, so by the time we iterate to one of them
    # we may have already moved it. libxml2's COPYING and Copyright are affected by this.
    next unless filename.exist?
    filename.chmod 0644
    install(filename)
  end
end

Creates symlinks to sources in this folder.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'Library/Homebrew/extend/pathname.rb', line 118

def install_symlink(*sources)
  sources.each do |src|
    case src
    when Array
      src.each { |s| install_symlink_p(s, File.basename(s)) }
    when Hash
      src.each { |s, new_basename| install_symlink_p(s, new_basename) }
    else
      install_symlink_p(src, File.basename(src))
    end
  end
end

#mach_o_bundle?Boolean



477
478
479
# File 'Library/Homebrew/extend/pathname.rb', line 477

def mach_o_bundle?
  false
end


363
364
365
366
# File 'Library/Homebrew/extend/pathname.rb', line 363

def make_relative_symlink(src)
  dirname.mkpath
  File.symlink(src.relative_path_from(dirname), self)
end

#old_writeObject



141
# File 'Library/Homebrew/extend/pathname.rb', line 141

alias old_write write

#resolved_pathObject



348
349
350
# File 'Library/Homebrew/extend/pathname.rb', line 348

def resolved_path
  symlink? ? dirname.join(readlink) : self
end

#resolved_path_exists?Boolean



353
354
355
356
357
358
359
360
# File 'Library/Homebrew/extend/pathname.rb', line 353

def resolved_path_exists?
  link = readlink
rescue ArgumentError
  # The link target contains NUL bytes
  false
else
  dirname.join(link).exist?
end

#rmdir_if_possibleObject

I don't trust the children.length == 0 check particularly, not to mention it is slow to enumerate the whole directory just to see if it is empty, instead rely on good ol' libc and the filesystem



248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'Library/Homebrew/extend/pathname.rb', line 248

def rmdir_if_possible
  rmdir
  true
rescue Errno::ENOTEMPTY
  if (ds_store = join(".DS_Store")).exist? && children.length == 1
    ds_store.unlink
    retry
  else
    false
  end
rescue Errno::EACCES, Errno::ENOENT, Errno::EBUSY
  false
end

#sha256Object



326
327
328
329
# File 'Library/Homebrew/extend/pathname.rb', line 326

def sha256
  require "digest/sha2"
  incremental_hash(Digest::SHA256)
end

#stemObject

for filetypes we support, basename without extension



240
241
242
# File 'Library/Homebrew/extend/pathname.rb', line 240

def stem
  File.basename((path = to_s), extname(path))
end

#subdirsObject



343
344
345
# File 'Library/Homebrew/extend/pathname.rb', line 343

def subdirs
  children.select(&:directory?)
end

#text_executable?Boolean



310
311
312
# File 'Library/Homebrew/extend/pathname.rb', line 310

def text_executable?
  /^#!\s*\S+/ =~ open("r") { |f| f.read(1024) }
end

#uninstall_infoObject



395
396
397
# File 'Library/Homebrew/extend/pathname.rb', line 395

def uninstall_info
  quiet_system "/usr/bin/install-info", "--delete", "--quiet", to_s, "#{dirname}/dir"
end

#verify_checksum(expected) ⇒ Object



331
332
333
334
335
# File 'Library/Homebrew/extend/pathname.rb', line 331

def verify_checksum(expected)
  raise ChecksumMissingError if expected.nil? || expected.empty?
  actual = Checksum.new(expected.hash_type, send(expected.hash_type).downcase)
  raise ChecksumMismatchError.new(self, expected, actual) unless expected == actual
end

#versionObject



263
264
265
266
# File 'Library/Homebrew/extend/pathname.rb', line 263

def version
  require "version"
  Version.parse(basename)
end

#write(content, *open_args) ⇒ Object

we assume this pathname object is a file obviously



145
146
147
148
149
# File 'Library/Homebrew/extend/pathname.rb', line 145

def write(content, *open_args)
  raise "Will not overwrite #{self}" if exist?
  dirname.mkpath
  open("w", *open_args) { |f| f.write(content) }
end

#write_env_script(target, env) ⇒ Object

Writes an exec script that sets environment variables



417
418
419
420
421
422
423
424
425
# File 'Library/Homebrew/extend/pathname.rb', line 417

def write_env_script(target, env)
  env_export = ""
  env.each { |key, value| env_export += "#{key}=\"#{value}\" " }
  dirname.mkpath
  write <<~EOS
    #!/bin/bash
    #{env_export}exec "#{target}" "$@"
  EOS
end

#write_exec_script(*targets) ⇒ Object

Writes an exec script in this folder for each target pathname



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'Library/Homebrew/extend/pathname.rb', line 400

def write_exec_script(*targets)
  targets.flatten!
  if targets.empty?
    opoo "tried to write exec scripts to #{self} for an empty list of targets"
    return
  end
  mkpath
  targets.each do |target|
    target = Pathname.new(target) # allow pathnames or strings
    join(target.basename).write <<~EOS
      #!/bin/bash
      exec "#{target}" "$@"
    EOS
  end
end

#write_jar_script(target_jar, script_name, java_opts = "", java_version: nil) ⇒ Object

Writes an exec script that invokes a java jar



439
440
441
442
443
444
445
446
447
448
# File 'Library/Homebrew/extend/pathname.rb', line 439

def write_jar_script(target_jar, script_name, java_opts = "", java_version: nil)
  mkpath
  java_home = if java_version
    "JAVA_HOME=\"$(#{Language::Java.java_home_cmd(java_version)})\" "
  end
  join(script_name).write <<~EOS
    #!/bin/bash
    #{java_home}exec java #{java_opts} -jar #{target_jar} "$@"
  EOS
end