Hi,
Here's my solution for Ruby Quiz #34. It's my first one
Well, it's pretty simple..
To find out whether the file was run directly or required:
- if __FILE__ == $0
When whiteout is run directly, it does the following for each ARGV:
- Leaves the shebang intact
- Adds the "require 'whiteout'"
- Converts the rest of the file to whitespace
The conversion to whitespace is done like this:
- Chars in like "\n" and "\r" are ignored
- Each byte is converted to its bit representation
- So we have something like 01100001
- Then, it is converted to whitespace
- 0 results in a " " (space)
- 1 results in a "\t" (tab)
When whiteout was required:
- Opens the file of $0
- Skips to after the require line
- Decodes the whitespace to code
- Runs the code with eval
I don't like the opening of $0 and the eval part.
Also, the encoding to whitespace could be made more efficient in size by adding more whitespace characters to the "code table".
I'm curious to see the other, probably cleaner solutions.
Bye,
聽聽Robin
PS: Here's the code:
#!/usr/bin/ruby
路路路
#
# This is my solution for Ruby Quiz #34, Whiteout.
# Author:: Robin Stocker
#
#
# The Whiteout module includes all functionality like:
# - whiten
# - run
# - encode
# - decode
#
module Whiteout
聽聽聽@@bit_to_code = { '0' => " ", '1' => "\t" }
聽聽聽@@code_to_bit = @@bit_to_code.invert
聽聽聽@@chars_to_ignore = [ "\n", "\r" ]
聽聽聽#
聽聽聽# Whitens the content of a file specified by _filename_.
聽聽聽# It leaves the shebang intact, if there is one.
聽聽聽# At the beginning of the file it inserts the require 'whiteout'.
聽聽聽# See #encode for details about how the whitening works.
聽聽聽#
聽聽聽def Whiteout.whiten( filename )
聽聽聽聽聽code = ''
聽聽聽聽聽File.open( filename, 'r' ) do |file|
聽聽聽聽聽聽聽file.each_line do |line|
聽聽聽聽聽聽聽聽聽if code.empty?
聽聽聽聽聽聽聽聽聽聽聽# Add shebang if there is one.
聽聽聽聽聽聽聽聽聽聽聽code << line if line =~ /#!\s*.+/
聽聽聽聽聽聽聽聽聽聽聽code << "#{$/}require 'whiteout'#{$/}"
聽聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽聽code << encode( line )
聽聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽end
聽聽聽聽聽end
聽聽聽聽聽File.open( filename, 'w' ) do |file|
聽聽聽聽聽聽聽file.write( code )
聽聽聽聽聽end
聽聽聽end
聽聽聽#
聽聽聽# Reads the file _filename_, decodes and runs it through eval.
聽聽聽#
聽聽聽def Whiteout.run( filename )
聽聽聽聽聽text = ''
聽聽聽聽聽File.open( filename, 'r' ) do |file|
聽聽聽聽聽聽聽decode = false
聽聽聽聽聽聽聽file.each_line do |line|
聽聽聽聽聽聽聽聽聽if not decode
聽聽聽聽聽聽聽聽聽聽聽# We don't want to decode the "require 'whiteout'",
聽聽聽聽聽聽聽聽聽聽聽# so start decoding not before we passed it.
聽聽聽聽聽聽聽聽聽聽聽decode = true if line =~ /require 'whiteout'/
聽聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽聽text << decode( line )
聽聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽end
聽聽聽聽聽end
聽聽聽聽聽# Run the code!
聽聽聽聽聽eval text
聽聽聽end
聽聽聽#
聽聽聽# Encodes text to "whitecode". It works like this:
聽聽聽# - Chars in @@char_to_ignore are ignored
聽聽聽# - Each byte is converted to its bit representation,
聽聽聽# so that we have something like 01100001
聽聽聽# - Then, it is converted to whitespace according to @@bit_to_code
聽聽聽# - 0 results in a " " (space)
聽聽聽# - 1 results in a "\t" (tab)
聽聽聽#
聽聽聽def Whiteout.encode( text )
聽聽聽聽聽white = ''
聽聽聽聽聽text.scan(/./m) do |char|
聽聽聽聽聽聽聽if @@chars_to_ignore.include?( char )
聽聽聽聽聽聽聽聽聽white << char
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽char.unpack('B8').first.scan(/./) do |bit|
聽聽聽聽聽聽聽聽聽聽聽code = @@bit_to_code[bit]
聽聽聽聽聽聽聽聽聽聽聽white << code
聽聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽end
聽聽聽聽聽end
聽聽聽聽聽return white
聽聽聽end
聽聽聽#
聽聽聽# Does the inverse of #encode, it takes "white"
聽聽聽# and returns the decoded text.
聽聽聽#
聽聽聽def Whiteout.decode( white )
聽聽聽聽聽text = ''
聽聽聽聽聽char = ''
聽聽聽聽聽white.scan(/./m) do |code|
聽聽聽聽聽聽聽if @@chars_to_ignore.include?( code )
聽聽聽聽聽聽聽聽聽text << code
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽char << @@code_to_bit[code]
聽聽聽聽聽聽聽聽聽if char.length == 8
聽聽聽聽聽聽聽聽聽聽聽text << [char].pack("B8")
聽聽聽聽聽聽聽聽聽聽聽char = ''
聽聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽end
聽聽聽聽聽end
聽聽聽聽聽return text
聽聽聽end
end
#
# And here's the logic part of whiteout.
# If it was run directly, whites out the files in ARGV.
# And if it was required, decodes the whitecode and runs it.
#
if __FILE__ == $0
聽聽聽ARGV.each do |filename|
聽聽聽聽聽Whiteout.whiten( filename )
聽聽聽end
else
聽聽聽Whiteout.run( $0 )
end