# In order to give the user maximum flexibility with # units, we create classes Quantity, Length and Angle class Quantity def initialize @conversions = {} end def normalize(mag, units = nil) units ||= default_units units = units.strip.downcase raise "Invalid unit #{units}" unless @conversions.has_key? units mag.to_f * @conversions[units] end def parse(args) num = nil units = nil # does it look like a number with a unit suffix? front = args.shift if front =~ /^(-?[0-9\.]+)([a-zA-Z-]+)$/ num = $1.to_f units = $2.strip.downcase # Just a number? elsif front =~ /^(-?[0-9\.]+)$/ num = $1.to_f # A non-blank, non-null junk value? elsif front and front !~ /^\s*$/ $stderr.puts "Error: invalid quantity #{front}" usage! end # Perhaps they put a space before the unit if !units and !args.empty? and @conversions.has_key? args.first units = args.shift.strip.downcase end @last_units ||= default_units units ||= @last_units @last_units = units return normalize(num,units) end def input parse( $stdin.readline.strip.split ) end def supported_units @conversions.keys end end class Length < Quantity CM_PER_IN = 2.54 IN_PER_CM = 1.0 / CM_PER_IN IN_PER_FT = 12.0 PTS_PER_IN = 72.0 CENTI = 100.0 MILLI = 1000.0 def initialize super @conversions = { 'in' => PTS_PER_IN, 'ft' => IN_PER_FT * PTS_PER_IN, 'mm' => CENTI / MILLI * IN_PER_CM * PTS_PER_IN, 'cm' => IN_PER_CM * PTS_PER_IN, 'm' => CENTI * IN_PER_CM * PTS_PER_IN } end def default_units 'in' end end class Diameter < Length DIAM_PER_CIRC = 1.0 / Math::PI DIAM_PER_RAD = 2.0 def initialize super @conversions.keys.each do |key| @conversions[key + '-circ'] = DIAM_PER_CIRC * @conversions[key] @conversions[key + '-rad'] = DIAM_PER_RAD * @conversions[key] end end end class Angle < Quantity TWOPI = 2.0 * Math::PI def initialize super @conversions = {'deg' => TWOPI / 360.0, 'degrees' => TWOPI / 360.0, 'rad' => 1.0, 'radians' => 1.0} end def default_units 'degrees' end end ANGLE = Angle.new LENGTH = Length.new DIAMETER = Diameter.new