class Object
Constants
- Enumerator
- Inf
- Infinity
- Number
- RbConfig
- Slop
Public Class Methods
Slightly gross hack to add a class method.
# File lib/epitools/minimal.rb, line 105 def self.alias_class_method(dest, src) metaclass.send(:alias_method, dest, src) end
Creates attr_accessors and an initialize method that accepts the attrs as arguments. (It’s kinda like an inline version of Struct.new(*args))
# File lib/epitools/core_ext/object.rb, line 58 def self.attrs(*names) attr_accessor *names define_method :initialize do |*vals| names.zip(vals) do |name, val| instance_variable_set("@#{name}", val) end end end
Turns block-accepting iterator methods (eg: each) into methods that return an Enumerator
when they’re called called without a block.
It
can transform many methods at once (eg: ‘enumerable :each, :all_people`).
Example:
def lots_of_stuff @stuff.each { |thing| yield thing } end enumerable :lots_of_stuff
Now you can use it like an Enumerator: object.lots_of_stuff.with_index.sort.zip(99..1000)
# File lib/epitools/minimal.rb, line 125 def self.enumerable *meths meths.each do |meth| alias_method "#{meth}_without_enumerator", meth class_eval %{ def #{meth}(*args, &block) return to_enum(#{meth.inspect}, *args, &block) unless block_given? #{meth}_without_enumerator(*args, &block) end } end end
Public Instance Methods
Fraction(a,b) is a wrapper for Fraction
# File lib/epitools/fraction.rb, line 129 def Fraction(*args) Fraction.new(*args) end
Path(“/some/path”) is a wrapper for Path
# File lib/epitools/minimal.rb, line 280 def Path(arg) Path[arg] end
# File lib/epitools/typed_struct.rb, line 139 def TypedStruct(schema) TypedStruct[schema] end
Slice the bits of an integer by passing a range (eg: 1217389172842 #=> [0, 1, 0, 1, 0, 1])
# File lib/epitools/core_ext/numbers.rb, line 424 def [](arg) case arg when Integer bit(arg) when Range bits[arg] end end
This method is convenience for the ‘File.expand_path(File.dirname(__FILE__))` idiom. (taken from Michael Fellinger’s Ramaze… thanx, dood! :D)
# File lib/epitools/minimal.rb, line 27 def __DIR__(*args) filename = caller[0][/^(.*):/, 1] dir = File.expand_path(File.dirname(filename)) ::File.expand_path(::File.join(dir, *args.map{|a| a.to_s})) end
# File lib/epitools/minimal.rb, line 178 def ancestors self.class.ancestors end
Automatically install a required commandline tool using the platform’s package manager (incomplete – only supports debian :)
-
apt-get on debian/ubuntu
-
yum on fedora
-
fink/ports on OSX
-
cygwin on windows
# File lib/epitools/clitools.rb, line 162 def autoinstall(*packages) all_packages_installed = packages.all? { |pkg| Path.which pkg } unless all_packages_installed cmd(["sudo apt-get install ?", packages.join(' ')]) end end
‘autoreq’ is a replacement for autoload that can load gems.
Usage:
autoreq :Constant, 'thing-to-require' autoreq :Constant, 'thing-to-require' autoreq :OtherConstant do gem 'somegem', '~> 1.2' require 'somegem' end
# File lib/epitools/minimal.rb, line 45 def autoreq(const, path=nil, &block) raise "Error: autoreq must be supplied with a file to load, or a block." unless !!path ^ block_given? if block_given? Module.autoreqs[const] = block else Module.autoreqs[const] = path end end
Quick and easy benchmark.
Examples:
bench { something } bench(90000000) { something } bench :fast => proc { fast_method }, :slow => proc { slow_method }
In Ruby 1.9:
bench fast: ->{ fast_method }, slow: ->{ slow_method }
# File lib/epitools/core_ext/object.rb, line 127 def bench(*args, &block) # Shitty perl-esque hack to let the method take all the different kind of arguments. opts = Hash === args.last ? args.pop : {} n = args.first || 100 if opts.any? raise "Error: Supply either a do/end block, or procs as options. Not both." if block_given? raise "Error: Options must be procs." unless opts.all? { |k, v| v.is_a?(Proc) } benchblock = proc do |bm| opts.each do |name, blk| bm.report(name.to_s) { n.times &blk } end end elsif block_given? benchblock = proc do |bm| bm.report { n.times &block } end else raise "Error: Must supply some code to benchmark." end puts "* Benchmarking #{n} iterations..." Benchmark.bmbm(&benchblock) end
# File lib/epitools/core_ext/numbers.rb, line 446 def big_endian_bytes bytes.reverse end
Convert the integer into its sequence of bytes (little endian format: lowest-order-byte first)
TODO: This could be made much more efficient!
# File lib/epitools/core_ext/numbers.rb, line 438 def bytes nbytes = (bit_length / 8.0).ceil (0..nbytes).map do |current_byte| (self >> (8 * current_byte)) & 0xFF end end
# File lib/epitools/clitools.rb, line 258 def cached_curl(url) cache = "/tmp/curl-#{url.sha1}.cache" if File.exist?(cache) $stderr.puts "cached! => #{cache}" else File.write(cache, curl(url)) end File.read(cache) end
Defines an instance method within a class
# File lib/epitools/minimal.rb, line 98 def class_def name, &blk class_eval { define_method name, &blk } end
Execute a ‘system()` command using SQL-style escaped arguments.
Example:
cmd( ["cp -arv ? ?", "/usr/src", "/home/you/A Folder/dest"] )
Which is equivalent to:
system( "cp", "-arv", "/usr/src", "/home/you/A Folder/dest" )
Notice that you don’t need to shell-escape anything. That’s done automagically!
If you don’t pass any arrays, ‘cmd` works the same as `system`:
cmd( "cp", "-arv", "etc", "etc" )
# File lib/epitools/clitools.rb, line 81 def cmd(*args) cmd_args = [] for arg in args case arg when Array cmd_literals = arg.shift.split(/\s+/) for cmd_literal in cmd_literals if cmd_literal == "?" raise "Not enough substitution arguments" unless cmd_args.any? cmd_args << arg.shift else cmd_args << cmd_literal end end raise "More parameters than ?'s in cmd string" if arg.any? when String cmd_args << arg else cmd_args << arg.to_s end end p [:cmd_args, cmd_args] if $DEBUG system(*cmd_args) end
# File lib/epitools/pretty_backtrace.rb, line 54 def color_backtrace_1(lines) groups = lines.split_before { |line,nextline| line.path != nextline.path } for group in groups dir, filename = File.split(group.first.path) puts "#{filename.green} (#{dir.light_white})" # /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb # 234: require | 553: new_constants_in | group.each do |line| puts " |_ #{line.num.magenta}: #{line.meth.light_yellow.underline}" end #puts " |_ " + group.map{|line| "#{line.num.magenta}: #{line.meth.light_yellow}"}.join(' | '.red) end end
# File lib/epitools/pretty_backtrace.rb, line 70 def color_backtrace_2(lines, **options) groups = lines.reverse.split_at { |line,nextline| line.path != nextline.path } if options[:no_gems] groups = groups.split_when { |a, nexta| a.first.gem? != nexta.first.gem? } groups.map! { |group| group.first.gem? ? [] : group } end for group in groups if group.empty? puts " ... ignored ... " puts next end firstline = group.first # custom_require.rb (/usr/local/lib/site_ruby/1.8/rubygems) # 234: require => super # 553: new_constants_in => #puts "#{firstline.filename.green} (#{firstline.dir.light_white})" puts "#{firstline.filename.underline} (#{firstline.dir.light_white})" max_methsize = group.map { |line| line.meth.size}.max group.each do |line| pad = " " * (max_methsize - line.meth.size) puts " #{line.num.magenta}: #{line.meth.light_yellow}#{pad}" puts " #{"|_".blue} #{line.codeline.strip}" end puts end end
Colorized puts (see: ‘String#colorize`)
# File lib/epitools/clitools.rb, line 62 def cputs(*args) puts args.join("\n").colorize end
# File lib/epitools/clitools.rb, line 240 def curl(url) curl_open(url).read end
# File lib/epitools/clitools.rb, line 254 def curl_json(url) JSON.parse(curl(url)) end
# File lib/epitools/clitools.rb, line 244 def curl_open(url, **headers) # headers["User-Agent"] ||= "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/85 Version/11.1.1 Safari/605.1.15" cmd = ["curl", "-LSs"] headers.each { |k,v| cmd += ["-H", "#{k}: #{v}"] } cmd << url IO.popen(cmd) rescue Errno::ENOENT raise "Error: 'curl' isn't installed." end
# File lib/epitools/pretty_backtrace.rb, line 120 def debug_backtrace(lines) lines.each do |line| p line.path end end
Remove an object, method, constant, etc.
# File lib/epitools/minimal.rb, line 58 def del(x) case x when String del(x.to_sym) when Class, Module Object.send(:remove_const, x.name) when Method x.owner.send(:undef_method, x.name) when Symbol if Object.const_get(x) Object.send(:remove_const, x) elsif method(x) undef_method x end else raise "Error: don't know how to 'del #{x.inspect}'" end end
Emit a quick debug message (only if $DEBUG is true)
# File lib/epitools/core_ext/object.rb, line 92 def dmsg(msg) if $DEBUG case msg when String puts msg else puts msg.inspect end end end
Default “float?” behaviour.
# File lib/epitools/core_ext/truthiness.rb, line 12 def float?; false; end
Lookup GeoIP information (city, state, country, etc.) for an IP address or hostname
(Note: Must be able to find one of /usr/share/GeoIP/GeoIP{,City}.dat, or specified manually
as (an) extra argument(s).)
# File lib/epitools/clitools.rb, line 186 def geoip(addr, city_data='/usr/share/GeoIP/GeoIPCity.dat', country_data='/usr/share/GeoIP/GeoIP.dat') ( $geoip ||= begin if city_data and File.exist? city_data geo = GeoIP.new city_data proc { |addr| geo.city(addr) } elsif country_data and File.exist? country_data geo = GeoIP.new country_data proc { |addr| geo.country(addr) } else raise "Can't find GeoIP data files." end end ).call(addr) end
Instead of:
if cookie_jar.include? cookie
Now you can do:
if cookie.in? cookie_jar
# File lib/epitools/minimal.rb, line 144 def in?(enum) enum.include? self end
Default “integer?” behaviour.
# File lib/epitools/core_ext/truthiness.rb, line 7 def integer?; false; end
Create scrollable output via less!
This command runs ‘less` in a subprocess, and gives you the IO
to its STDIN
pipe so that you can communicate with it.
Example:
lesspipe do |less| 50.times { less.puts "Hi mom!" } end
The default less parameters are:
-
Allow colour
-
Don’t wrap lines longer than the screen
-
Quit immediately (without paging) if there’s less than one screen of text.
You can change these options by passing a hash to ‘lesspipe`, like so:
lesspipe(:wrap=>false) { |less| less.puts essay.to_s }
It
accepts the following boolean options:
:color => Allow ANSI colour codes? :wrap => Wrap long lines? :always => Always page, even if there's less than one page of text?
# File lib/epitools/clitools.rb, line 27 def lesspipe(*args) if args.any? and args.last.is_a?(Hash) options = args.pop else options = {} end output = args.first if args.any? params = [] params << "-R" unless options[:color] == false params << "-S" unless options[:wrap] == true params << "-F" unless options[:always] == true if options[:tail] == true params << "+\\>" $stderr.puts "Seeking to end of stream..." end params << "-X" IO.popen("less #{params * ' '}", "w") do |less| if output less.puts output else yield less end end rescue Errno::EPIPE, Interrupt # less just quit -- eat the exception. end
Return a hash of local variables in the caller’s scope: {:variable_name=>value}
# File lib/epitools/core_ext/object.rb, line 6 def locals require 'binding_of_caller' caller = binding.of_caller(1) vars = caller.eval("local_variables").reject{|e| e[/^_/]} vals = caller.eval("[ #{vars.join(",")} ]") Hash[ vars.zip(vals) ] end
Return the longest common prefix between two strings.
# File lib/epitools/lcs.rb, line 2 def longest_common_prefix(strings) p = nil strings.map{|s|s.size}.min.times do |c| if strings.map{|s|s[c]}.uniq.size == 1 p = c else break end end strings.first[0..p] unless p.nil? end
# File lib/epitools/lcs.rb, line 16 def longest_common_subsequence(s1, s2) num = Array.new(s1.size) { Array.new(s2.size) } len, ans = 0 s1.chars.each_with_index do |l1, i| s2.chars.each_with_index do |l2, j| unless l1==l2 num[i][j]=0 else if (i==0 || j==0) num[i][j] = 1 else num[i][j] = 1 + num[i-1][j-1] end len = ans = num[i][j] if num[i][j] > len end end end ans end
Serialize this object to a binary String
, using Marshal.dump.
# File lib/epitools/core_ext/object.rb, line 70 def marshal Marshal.dump self end
Adds methods to a metaclass
# File lib/epitools/minimal.rb, line 93 def meta_def name, &blk meta_eval { define_method name, &blk } end
# File lib/epitools/minimal.rb, line 88 def meta_eval &blk metaclass.instance_eval &blk end
# File lib/epitools/minimal.rb, line 81 def metaclass class << self self end end
# File lib/epitools/niceprint.rb, line 1 def niceprint(o, level=0, indent_first_line=true) maxstring = 50 maxarray = 20 result = "" dent = " " indent = dent * level result << indent if indent_first_line case o when Hash #puts "Hash!" result << "{\n" o.each_with_index do |(k,v),i| result << "#{indent+dent}#{k.inspect} => #{niceprint(v,level+1,false)}" result << "," unless i == o.size result << "\n" end result << "#{indent}}" when Array #puts "Array!" indent_first = o.any? { |e| e.instance_of? Hash } if indent_first result << "[\n" else result << "[" end o = o[0..maxarray] if o.size > maxarray o.each do |e| result << niceprint(e,level+1,indent_first) result << ", " end result << "]" when String #puts "String!" o = o[0..maxstring] + "..." if o.size > maxstring result << o.inspect else result << o.inspect end if level == 0 print result else result end end
Negates a boolean, chained-method style.
Example:
>> 10.even? => true >> 10.not.even? => false
# File lib/epitools/core_ext/object.rb, line 172 def not NotWrapper.new(self) end
Executes notify-send to create a desktop notification on the user’s desktop
Note: the ‘icon’ argument is the path to an image file
# File lib/epitools/clitools.rb, line 224 def notify_send(title, body=nil, icon: nil, time: 5) $stderr.puts "* #{title}" $stderr.puts " |_ #{body}" if body time_in_ms = time * 1000 cmd = ["notify-send"] cmd << "--expire-time=#{time_in_ms}" cmd << "--app-name=#{Process.argv0}" cmd << "--icon=#{icon}" if icon cmd << CGI.escapeHTML(title) cmd << CGI.escapeHTML(body) if body fork { system *cmd } end
Default “number?” behaviour.
# File lib/epitools/core_ext/truthiness.rb, line 17 def number?; false; end
# File lib/epitools/pretty_backtrace.rb, line 38 def parse_lines(backtrace) backtrace.map do |line| case line when /^\s+(.+):(\d+):in \`(.+)'/ Line.new($1, $2, $3) when /^\s+(.+):(\d+)/ Line.new($1, $2, '') when /^\s+$/ next else raise "huh?!" end end.compact end
Returns all the ‘size`-sized selections of the elements from an array.
I can’t remember why I wrote it like this, but the array you want to permute is passed in as a block. For example:
>> perms(1) { [1,2,3,4] } => [[1], [2], [3], [4]] >> perms(2) { [1,2,3,4] } => [[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4]]
The block also gets passed a parameter: the depth of the recursion. I can’t remember why I did that either! :D
# File lib/epitools/permutations.rb, line 66 def perms(size, n=0, stack=[], &block) ps = yield(n) results = [] if n >= size results << stack else ps.each do |p| results += perms(size, n+1, stack + [p], &block) end end results end
# File lib/epitools/core_ext/object.rb, line 158 def present? true end
Return an infinite enumerable of primes
# File lib/epitools/core_ext/numbers.rb, line 515 def primes Prime.instance end
Prompt the user for confirmation.
Usage:
prompt("Do you want a cookie?", "Ynqa") #=> returns the letter that the user pressed, in lowercase (and returns the default, 'y', if the user hits ENTER)
# File lib/epitools/clitools.rb, line 125 def prompt(message="Are you sure?", options="Yn") opts = options.scan(/./) optstring = opts.join("/") # case maintained defaults = opts.select{|o| o.upcase == o } opts = opts.map{|o| o.downcase} raise "Error: Too many default values for the prompt: #{default.inspect}" if defaults.size > 1 default = defaults.first loop do print "#{message} (#{optstring}) " response = STDIN.gets.strip.downcase case response when *opts return response when "" return default.downcase else puts " |_ Invalid option: #{response.inspect}. Try again." end end end
# File lib/epitools/pretty_backtrace.rb, line 108 def python_backtrace(lines) #groups = lines.reverse.split_when { |line,nextline| line.path != nextline.path } lines = lines.reverse puts "Traceback (most recent call last):" for line in lines puts %{ File "#{line.path}", line #{line.num}, in #{line.meth}} puts %{ #{line.codeline.strip}} end end
Return this object. If given a block, yields this object and returns the result of the block.
eg: stuff.group_by(&:self)
# File lib/epitools/minimal.rb, line 170 def self if block_given? yield self else self end end
Re-execute the script using sudo if it’s not currently running as root.
# File lib/epitools/clitools.rb, line 174 def sudoifnotroot unless Process.uid == 0 exit system("sudo", $PROGRAM_NAME, *ARGV) end end
Time
a block.
# File lib/epitools/core_ext/object.rb, line 106 def time(message=nil) start = Time.now result = yield elapsed = Time.now - start print "[#{message}] " if message puts "elapsed time: %0.5fs" % elapsed result end
Serialize this object to a Python bencoded (pickled) string
# File lib/epitools/core_ext/object.rb, line 179 def to_bencode BEncode.dump(self) end
Serialize this object to JSON (defaults to “pretty” indented JSON).
# File lib/epitools/core_ext/object.rb, line 85 def to_json(pretty=true) pretty ? JSON::pretty_generate(self) : JSON.dump(self) end
Serialize this object to YAML.
# File lib/epitools/core_ext/object.rb, line 78 def to_yaml YAML::dump(self) end
‘truthy?` means `not blank?`
# File lib/epitools/core_ext/truthiness.rb, line 22 def truthy? if respond_to? :blank? not blank? else not nil? end end
Instead of:
@person ? @person.name : nil
Now you can do:
@person.try(:name)
# File lib/epitools/minimal.rb, line 160 def try(method, *args, &block) send(method, *args, &block) if respond_to? method end
Search the PATH environment variable for binaries, returning the first one that exists
# File lib/epitools/clitools.rb, line 208 def which(*bins) bins.flatten.each do |bin| ENV["PATH"].split(":").each do |dir| full_path = File.join(dir, bin) return full_path if File.exist? full_path end end nil end
Gives you a copy of the object with its attributes changed to whatever was passed in the options hash.
Example:
>> cookie = Cookie.new(:size=>10, :chips=>200) => #<Cookie:0xffffffe @size=10, @chips=200> >> cookie.with(:chips=>50) => #<Cookie:0xfffffff @size=10, @chips=50>
(All this method does is dup the object, then call “key=(value)” for each key/value in the options hash.)
BONUS FEATURE! ==¶ ↑
If you supply a block, it just gives you the object, and returns whatever your block returns.
Example:
>> {a: 10, b: 2}.with { |hash| hash[:a] / hash[:b] } => 5
Good for chaining lots of operations together in a REPL.
# File lib/epitools/core_ext/object.rb, line 42 def with(**options) if block_given? yield self else obj = dup options.each { |key, value| obj.send "#{key}=", value } obj end end
# File lib/epitools/core_ext/object.rb, line 14 def write_to(file) open(file, "wb") { |f| f.write self.to_s } end