Skip to content

Instantly share code, notes, and snippets.

@webstandardcss
Forked from ttscoff/sizes.rb
Created April 18, 2019 15:32
Show Gist options
  • Save webstandardcss/fb9219373ea70efd8378230b0a97ca47 to your computer and use it in GitHub Desktop.
Save webstandardcss/fb9219373ea70efd8378230b0a97ca47 to your computer and use it in GitHub Desktop.
sizes: Calculate and sort all filesizes for current folder
#!/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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment