Skip to main content

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
2 files, 342 additions, 3 deletions

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)