mirror of
https://github.com/danbee/dotfiles-local
synced 2025-03-04 08:49:07 +00:00
Add Brett Terpstra's sizes command
This commit is contained in:
parent
e0129c900a
commit
9114f9e2f4
332
bin/sizes
Executable file
332
bin/sizes
Executable file
@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Sizes - Calculate and sort all filesizes for current folder
|
||||
# Includes directory sizes, colorized output
|
||||
# Brett Terpstra 2019 WTF License
|
||||
VERSION = "1.0.0"
|
||||
|
||||
require 'shellwords'
|
||||
|
||||
# Just including term-ansicolor by @flori and avoiding all the
|
||||
# rigamarole of requiring multiple files when it's not a gem... - Brett
|
||||
#
|
||||
# ansicolor Copyright: Florian Frank
|
||||
# License: <https://github.com/flori/term-ansicolor/blob/master/COPYING>
|
||||
# Home: <https://github.com/flori/term-ansicolor>
|
||||
module Term
|
||||
|
||||
# The ANSIColor module can be used for namespacing and mixed into your own
|
||||
# classes.
|
||||
module ANSIColor
|
||||
# require 'term/ansicolor/version'
|
||||
|
||||
# :stopdoc:
|
||||
ATTRIBUTES = [
|
||||
[ :clear , 0 ], # String#clear is already used to empty string in Ruby 1.9
|
||||
[ :reset , 0 ], # synonym for :clear
|
||||
[ :bold , 1 ],
|
||||
[ :dark , 2 ],
|
||||
[ :italic , 3 ], # not widely implemented
|
||||
[ :underline , 4 ],
|
||||
[ :underscore , 4 ], # synonym for :underline
|
||||
[ :blink , 5 ],
|
||||
[ :rapid_blink , 6 ], # not widely implemented
|
||||
[ :negative , 7 ], # no reverse because of String#reverse
|
||||
[ :concealed , 8 ],
|
||||
[ :strikethrough , 9 ], # not widely implemented
|
||||
[ :black , 30 ],
|
||||
[ :red , 31 ],
|
||||
[ :green , 32 ],
|
||||
[ :yellow , 33 ],
|
||||
[ :blue , 34 ],
|
||||
[ :magenta , 35 ],
|
||||
[ :cyan , 36 ],
|
||||
[ :white , 37 ],
|
||||
[ :on_black , 40 ],
|
||||
[ :on_red , 41 ],
|
||||
[ :on_green , 42 ],
|
||||
[ :on_yellow , 43 ],
|
||||
[ :on_blue , 44 ],
|
||||
[ :on_magenta , 45 ],
|
||||
[ :on_cyan , 46 ],
|
||||
[ :on_white , 47 ],
|
||||
[ :intense_black , 90 ], # High intensity, aixterm (works in OS X)
|
||||
[ :intense_red , 91 ],
|
||||
[ :intense_green , 92 ],
|
||||
[ :intense_yellow , 93 ],
|
||||
[ :intense_blue , 94 ],
|
||||
[ :intense_magenta , 95 ],
|
||||
[ :intense_cyan , 96 ],
|
||||
[ :intense_white , 97 ],
|
||||
[ :on_intense_black , 100 ], # High intensity background, aixterm (works in OS X)
|
||||
[ :on_intense_red , 101 ],
|
||||
[ :on_intense_green , 102 ],
|
||||
[ :on_intense_yellow , 103 ],
|
||||
[ :on_intense_blue , 104 ],
|
||||
[ :on_intense_magenta , 105 ],
|
||||
[ :on_intense_cyan , 106 ],
|
||||
[ :on_intense_white , 107 ]
|
||||
]
|
||||
|
||||
ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
|
||||
# :startdoc:
|
||||
|
||||
# Returns true if Term::ANSIColor supports the +feature+.
|
||||
#
|
||||
# The feature :clear, that is mixing the clear color attribute into String,
|
||||
# is only supported on ruby implementations, that do *not* already
|
||||
# implement the String#clear method. It's better to use the reset color
|
||||
# attribute instead.
|
||||
def support?(feature)
|
||||
case feature
|
||||
when :clear
|
||||
!String.instance_methods(false).map(&:to_sym).include?(:clear)
|
||||
end
|
||||
end
|
||||
# Returns true, if the coloring function of this module
|
||||
# is switched on, false otherwise.
|
||||
def self.coloring?
|
||||
@coloring
|
||||
end
|
||||
|
||||
# Turns the coloring on or off globally, so you can easily do
|
||||
# this for example:
|
||||
# Term::ANSIColor::coloring = STDOUT.isatty
|
||||
def self.coloring=(val)
|
||||
@coloring = val
|
||||
end
|
||||
self.coloring = true
|
||||
|
||||
ATTRIBUTES.each do |c, v|
|
||||
eval <<-EOT
|
||||
def #{c}(string = nil)
|
||||
result = ''
|
||||
result << "\e[#{v}m" if Term::ANSIColor.coloring?
|
||||
if block_given?
|
||||
result << yield
|
||||
elsif string.respond_to?(:to_str)
|
||||
result << string.to_str
|
||||
elsif respond_to?(:to_str)
|
||||
result << to_str
|
||||
else
|
||||
return result #only switch on
|
||||
end
|
||||
result << "\e[0m" if Term::ANSIColor.coloring?
|
||||
result
|
||||
end
|
||||
EOT
|
||||
end
|
||||
|
||||
# Regular expression that is used to scan for ANSI-sequences while
|
||||
# uncoloring strings.
|
||||
COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/
|
||||
|
||||
# Returns an uncolored version of the string, that is all
|
||||
# ANSI-sequences are stripped from the string.
|
||||
def uncolored(string = nil) # :yields:
|
||||
if block_given?
|
||||
yield.to_str.gsub(COLORED_REGEXP, '')
|
||||
elsif string.respond_to?(:to_str)
|
||||
string.to_str.gsub(COLORED_REGEXP, '')
|
||||
elsif respond_to?(:to_str)
|
||||
to_str.gsub(COLORED_REGEXP, '')
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
module_function
|
||||
|
||||
# Returns an array of all Term::ANSIColor attributes as symbols.
|
||||
def attributes
|
||||
ATTRIBUTE_NAMES
|
||||
end
|
||||
extend self
|
||||
end
|
||||
end
|
||||
|
||||
# Begin sizes
|
||||
|
||||
class String
|
||||
|
||||
include Term::ANSIColor
|
||||
|
||||
# ensure trailing slash
|
||||
def slashit
|
||||
self.sub(/\/?$/,'/')
|
||||
end
|
||||
|
||||
# colorize a human readable size format by size
|
||||
def color_fmt
|
||||
case self
|
||||
when /\dB?$/
|
||||
self.blue
|
||||
when /\dKB?$/
|
||||
self.green
|
||||
when /\dMB?$/
|
||||
self.yellow
|
||||
when /\dGB?$/
|
||||
self.red
|
||||
else
|
||||
self.bold.red
|
||||
end
|
||||
end
|
||||
|
||||
# colorize files by type (directories and hidden files)
|
||||
def color_file(force_check=false)
|
||||
filename = self.dup
|
||||
if force_check && File.directory?(filename)
|
||||
filename.sub!(/\/?$/,'/')
|
||||
end
|
||||
|
||||
case filename
|
||||
when /\/$/
|
||||
filename.green
|
||||
when /^\./
|
||||
filename.white
|
||||
else
|
||||
filename.bold.white
|
||||
end
|
||||
end
|
||||
|
||||
# Replace $HOME in path with ~
|
||||
def short_dir
|
||||
home = ENV['HOME']
|
||||
self.sub(/#{home}/, '~')
|
||||
end
|
||||
|
||||
# Convert a line like `120414 filename` to a colorized string with
|
||||
# human readable size
|
||||
def line_to_human
|
||||
parts = self.split(/\t/)
|
||||
if parts[0] =~ /NO ACCESS/
|
||||
" ERROR".red + " " + parts[1].color_file
|
||||
else
|
||||
size = to_human(parts[0].to_i).color_fmt
|
||||
size.pad_escaped(7) + " " + parts[1].color_file
|
||||
end
|
||||
end
|
||||
|
||||
# Pad a line containing ansi escape codes to a given length, ignoring
|
||||
# the escape codes
|
||||
def pad_escaped(len)
|
||||
str = self.dup
|
||||
str.gsub!(/\e\[\d+m/,'')
|
||||
prefix = ""
|
||||
while prefix.length + str.length < len
|
||||
prefix += " "
|
||||
end
|
||||
prefix + self
|
||||
end
|
||||
end
|
||||
|
||||
# Convert a number (assumed bytes) to a human readable format (12.5K)
|
||||
def to_human(n,fmt=false)
|
||||
count = 0
|
||||
formats = %w(B K M G T P E Z Y)
|
||||
|
||||
while (fmt || n >= 1024) && count < 8
|
||||
n /= 1024.0
|
||||
count += 1
|
||||
break if fmt && formats[count][0].upcase =~ /#{fmt[0].upcase}/
|
||||
end
|
||||
|
||||
format("%.2f%s",n,formats[count])
|
||||
end
|
||||
|
||||
# Use `du` to size a single directory and all of its contents. This
|
||||
# number is returned in blocks (512B), so the human readable result may
|
||||
# be slightly different than you'd get from `ls` or a GUI file manager
|
||||
def du_size_single(dir)
|
||||
res = %x{du -s #{Shellwords.escape(dir)} 2>/dev/null}.strip
|
||||
if $?.success?
|
||||
parts = res.split(/\t/)
|
||||
(parts[0].to_i * 512).to_s + "\t" + parts[1].strip
|
||||
else
|
||||
"NO ACCESS\t#{dir}"
|
||||
end
|
||||
end
|
||||
|
||||
# main function
|
||||
def all_sizes(dir)
|
||||
# Use `ls` to list all files in the target with long info
|
||||
files = %x{ls -lSrAF #{dir.slashit} 2>/dev/null}
|
||||
unless $?.success?
|
||||
$stdout.puts "Error getting file listing".red
|
||||
Process.exit 1
|
||||
end
|
||||
files = files.strip.split(/\n/)
|
||||
|
||||
files.delete_if {|line|
|
||||
line.strip =~ /^total \d+/
|
||||
}
|
||||
|
||||
# trim file list to just size and filename
|
||||
files.map! {|line|
|
||||
line.sub(/\S{10,11} +\d+ +\S+ +\w+ +(\d+) +\w{3} +\d+ +[\d:]+ +(.*?)$/, "\\1\t\\2")
|
||||
}
|
||||
|
||||
|
||||
# if a line is a path to a directory, use `du` to update its size with
|
||||
# the total filesize of the directory contents.
|
||||
files.map! {|entry|
|
||||
file = entry.split(/\t/)[1]
|
||||
if File.directory?(file)
|
||||
du_size_single(file)
|
||||
else
|
||||
entry
|
||||
end
|
||||
}
|
||||
|
||||
# Sort by size (after updating directory sizes)
|
||||
files.sort! {|a,b|
|
||||
size1 = a.split(/\t/)[0].to_i
|
||||
size2 = b.split(/\t/)[0].to_i
|
||||
size1 <=> size2
|
||||
}
|
||||
|
||||
# Output each line with human-readable size and colorization
|
||||
files.each {|entry|
|
||||
$stdout.puts entry.line_to_human
|
||||
}
|
||||
# Include a total for the directory
|
||||
$stdout.puts "-------".black.bold
|
||||
$stdout.puts(du_size_single(dir).short_dir.line_to_human)
|
||||
end
|
||||
|
||||
def help
|
||||
app = File.basename(__FILE__)
|
||||
help =<<EOHELP
|
||||
|
||||
#{app.bold.white} #{VERSION.green} by Brett Terpstra
|
||||
Display a human-readable list of sizes for all files and directories.
|
||||
|
||||
usage:
|
||||
$ #{app.bold.white} [directory]
|
||||
|
||||
Leaving directory blank operates in the current working directory.
|
||||
|
||||
EOHELP
|
||||
puts help
|
||||
Process.exit 0
|
||||
end
|
||||
|
||||
# Assume operating on current directory...
|
||||
dir = ENV['PWD']
|
||||
|
||||
# ...unless an argument is provided
|
||||
if ARGV[0]
|
||||
# Add some help. Why not?
|
||||
if ARGV[0] =~ /^-?-h(elp)?/
|
||||
help
|
||||
elsif ARGV[0] =~ /^-?-v(ersion)?/
|
||||
$stdout.puts File.basename(__FILE__) + " v" + VERSION
|
||||
Process.exit 0
|
||||
else
|
||||
argdir = File.expand_path(ARGV[0])
|
||||
if File.directory?(argdir)
|
||||
dir = argdir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
all_sizes(dir)
|
||||
Loading…
Reference in New Issue
Block a user