tweak cdir to match Brett’s “sizes”
- ID
6589e23- date
2022-03-21 08:15:17+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
4d0a2f3- message
tweak cdir to match Brett's "sizes" Co-authored-by: Florian Frank <flori@ping.de> Co-authored-by: Brett Terpstra <me@brettterpstra.com>- changed files
Changed files
cdir (1345) → cdir (1398)
diff --git a/cdir b/cdir
index 17c8b6b..cf75b80 100755
--- a/cdir
+++ b/cdir
@@ -46,7 +46,9 @@ if __name__ == "__main__":
prefixes[e] = count_entries_under(e)
for prefix, count in reversed(prefixes.most_common()):
- print("%5d\t%s" % (count, prefix))
+ print("%7d\t%s" % (count, prefix))
- print("=====\t%s" % ("=" * max(len(p) for p in prefixes)))
- print("%5d" % sum(prefixes.values()))
\ No newline at end of file
+ home_display = os.path.abspath(".").replace(os.environ["HOME"], "~")
+
+ print("-------")
+ print("%7d\t%s" % (sum(prefixes.values()), home_display))
sizes (0) → sizes (9408)
diff --git a/sizes b/sizes
new file mode 100755
index 0000000..c4bc286
--- /dev/null
+++ b/sizes
@@ -0,0 +1,337 @@
+#!/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/)
+ {:size => parts[0].to_i * 512, :file => parts[1].strip}
+ else
+ {:size => nil, :file => 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
+ matches = files.map {|line|
+ # Examples of output I'm trying to match:
+ #
+ # -rw-r--r-- 1 alexwlchan staff 1053 1 Jul 2018 LICENSE
+ # -rw-r--r-- 1 alexwlchan staff 306 8 Jan 20:36 Makefile
+ #
+ m = line.match(/^\S{10,11} +\d+ +\S+ +\w+ +(?<size>\d+) +\d+ +\w{3} +[\d:]+ +(?<file>.*?)$/)
+ {:size => m[:size].to_i, :file => m[:file]}
+ }
+
+
+ # if a line is a path to a directory, use `du` to update its size with
+ # the total filesize of the directory contents.
+ matches.map! {|match|
+ if File.directory?(match[:file])
+ du_size_single(match[:file])
+ else
+ match
+ end
+ }
+
+ # Sort by size (after updating directory sizes)
+ matches.sort! {|a,b|
+ size1 = a[:size]
+ size2 = b[:size]
+ size1 <=> size2
+ }
+
+ # Output each line with human-readable size and colorization
+ matches.each {|match|
+ entry = "#{match[:size]}\t#{match[:file]}"
+ $stdout.puts entry.line_to_human
+ }
+ # Include a total for the directory
+ $stdout.puts "-------".black.bold
+ total = du_size_single(dir)
+ entry = "#{total[:size]}\t#{total[:file]}"
+ $stdout.puts(entry.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)