require 'net/http'
require 'uri'
+begin
+ $we_have_html_entities_decoder = require 'htmlentities'
+rescue LoadError
+ $we_have_html_entities_decoder = false
+ UNESCAPE_TABLE = {
+ 'raquo' => '>>',
+ 'quot' => '"',
+ 'apos' => '\'',
+ 'micro' => 'u',
+ 'copy' => '(c)',
+ 'trade' => '(tm)',
+ 'reg' => '(R)',
+ '#174' => '(R)',
+ '#8220' => '"',
+ '#8221' => '"',
+ '#8212' => '--',
+ '#39' => '\'',
+=begin
+ # extras codes, for future use...
+ 'zwnj' => '‌',
+ 'aring' => '\xe5',
+ 'gt' => '>',
+ 'yen' => '\xa5',
+ 'ograve' => '\xf2',
+ 'Chi' => 'Χ',
+ 'bull' => '•',
+ 'Egrave' => '\xc8',
+ 'Ntilde' => '\xd1',
+ 'upsih' => 'ϒ',
+ 'Yacute' => '\xdd',
+ 'asymp' => '≈',
+ 'radic' => '√',
+ 'otimes' => '⊗',
+ 'nabla' => '∇',
+ 'aelig' => '\xe6',
+ 'oelig' => 'œ',
+ 'equiv' => '≡',
+ 'Psi' => 'Ψ',
+ 'auml' => '\xe4',
+ 'circ' => 'ˆ',
+ 'Acirc' => '\xc2',
+ 'Epsilon' => 'Ε',
+ 'Yuml' => 'Ÿ',
+ 'Eta' => 'Η',
+ 'lt' => '<',
+ 'Icirc' => '\xce',
+ 'Upsilon' => 'Υ',
+ 'ndash' => '–',
+ 'there4' => '∴',
+ 'Prime' => '″',
+ 'prime' => '′',
+ 'psi' => 'ψ',
+ 'Kappa' => 'Κ',
+ 'rsaquo' => '›',
+ 'Tau' => 'Τ',
+ 'darr' => '↓',
+ 'ocirc' => '\xf4',
+ 'lrm' => '‎',
+ 'zwj' => '‍',
+ 'cedil' => '\xb8',
+ 'Ecirc' => '\xca',
+ 'not' => '\xac',
+ 'amp' => '&',
+ 'AElig' => '\xc6',
+ 'oslash' => '\xf8',
+ 'acute' => '\xb4',
+ 'lceil' => '⌈',
+ 'laquo' => '\xab',
+ 'shy' => '\xad',
+ 'rdquo' => '”',
+ 'ge' => '≥',
+ 'Igrave' => '\xcc',
+ 'Ograve' => '\xd2',
+ 'euro' => '€',
+ 'dArr' => '⇓',
+ 'sdot' => '⋅',
+ 'nbsp' => '\xa0',
+ 'lfloor' => '⌊',
+ 'lArr' => '⇐',
+ 'Auml' => '\xc4',
+ 'larr' => '←',
+ 'Atilde' => '\xc3',
+ 'Otilde' => '\xd5',
+ 'szlig' => '\xdf',
+ 'clubs' => '♣',
+ 'diams' => '♦',
+ 'agrave' => '\xe0',
+ 'Ocirc' => '\xd4',
+ 'Iota' => 'Ι',
+ 'Theta' => 'Θ',
+ 'Pi' => 'Π',
+ 'OElig' => 'Œ',
+ 'Scaron' => 'Š',
+ 'frac14' => '\xbc',
+ 'egrave' => '\xe8',
+ 'sub' => '⊂',
+ 'iexcl' => '\xa1',
+ 'frac12' => '\xbd',
+ 'sbquo' => '‚',
+ 'ordf' => '\xaa',
+ 'sum' => '∑',
+ 'prop' => '∝',
+ 'Uuml' => '\xdc',
+ 'ntilde' => '\xf1',
+ 'sup' => '⊃',
+ 'theta' => 'θ',
+ 'prod' => '∏',
+ 'nsub' => '⊄',
+ 'hArr' => '⇔',
+ 'rlm' => '‏',
+ 'THORN' => '\xde',
+ 'infin' => '∞',
+ 'yuml' => '\xff',
+ 'Mu' => 'Μ',
+ 'le' => '≤',
+ 'Eacute' => '\xc9',
+ 'thinsp' => ' ',
+ 'ecirc' => '\xea',
+ 'bdquo' => '„',
+ 'Sigma' => 'Σ',
+ 'fnof' => 'ƒ',
+ 'Aring' => '\xc5',
+ 'tilde' => '˜',
+ 'frac34' => '\xbe',
+ 'emsp' => ' ',
+ 'mdash' => '—',
+ 'uarr' => '↑',
+ 'permil' => '‰',
+ 'Ugrave' => '\xd9',
+ 'rarr' => '→',
+ 'Agrave' => '\xc0',
+ 'chi' => 'χ',
+ 'forall' => '∀',
+ 'eth' => '\xf0',
+ 'rceil' => '⌉',
+ 'iuml' => '\xef',
+ 'gamma' => 'γ',
+ 'lambda' => 'λ',
+ 'harr' => '↔',
+ 'rang' => '〉',
+ 'xi' => 'ξ',
+ 'dagger' => '†',
+ 'divide' => '\xf7',
+ 'Ouml' => '\xd6',
+ 'image' => 'ℑ',
+ 'alefsym' => 'ℵ',
+ 'igrave' => '\xec',
+ 'otilde' => '\xf5',
+ 'Oacute' => '\xd3',
+ 'sube' => '⊆',
+ 'alpha' => 'α',
+ 'frasl' => '⁄',
+ 'ETH' => '\xd0',
+ 'lowast' => '∗',
+ 'Nu' => 'Ν',
+ 'plusmn' => '\xb1',
+ 'Euml' => '\xcb',
+ 'real' => 'ℜ',
+ 'sup1' => '\xb9',
+ 'sup2' => '\xb2',
+ 'sup3' => '\xb3',
+ 'Oslash' => '\xd8',
+ 'Aacute' => '\xc1',
+ 'cent' => '\xa2',
+ 'oline' => '‾',
+ 'Beta' => 'Β',
+ 'perp' => '⊥',
+ 'Delta' => 'Δ',
+ 'loz' => '◊',
+ 'pi' => 'π',
+ 'iota' => 'ι',
+ 'empty' => '∅',
+ 'euml' => '\xeb',
+ 'brvbar' => '\xa6',
+ 'iacute' => '\xed',
+ 'para' => '\xb6',
+ 'micro' => '\xb5',
+ 'cup' => '∪',
+ 'weierp' => '℘',
+ 'uuml' => '\xfc',
+ 'part' => '∂',
+ 'icirc' => '\xee',
+ 'delta' => 'δ',
+ 'omicron' => 'ο',
+ 'upsilon' => 'υ',
+ 'Iuml' => '\xcf',
+ 'Lambda' => 'Λ',
+ 'Xi' => 'Ξ',
+ 'kappa' => 'κ',
+ 'ccedil' => '\xe7',
+ 'Ucirc' => '\xdb',
+ 'cap' => '∩',
+ 'mu' => 'μ',
+ 'scaron' => 'š',
+ 'lsquo' => '‘',
+ 'isin' => '∈',
+ 'Zeta' => 'Ζ',
+ 'supe' => '⊇',
+ 'deg' => '\xb0',
+ 'and' => '∧',
+ 'tau' => 'τ',
+ 'pound' => '\xa3',
+ 'hellip' => '…',
+ 'curren' => '\xa4',
+ 'int' => '∫',
+ 'ucirc' => '\xfb',
+ 'rfloor' => '⌋',
+ 'ensp' => ' ',
+ 'crarr' => '↵',
+ 'ugrave' => '\xf9',
+ 'notin' => '∉',
+ 'exist' => '∃',
+ 'uArr' => '⇑',
+ 'cong' => '≅',
+ 'Dagger' => '‡',
+ 'oplus' => '⊕',
+ 'times' => '\xd7',
+ 'atilde' => '\xe3',
+ 'piv' => 'ϖ',
+ 'ni' => '∋',
+ 'Phi' => 'Φ',
+ 'lsaquo' => '‹',
+ 'Uacute' => '\xda',
+ 'Omicron' => 'Ο',
+ 'ang' => '∠',
+ 'ne' => '≠',
+ 'iquest' => '\xbf',
+ 'eta' => 'η',
+ 'yacute' => '\xfd',
+ 'Rho' => 'Ρ',
+ 'uacute' => '\xfa',
+ 'Alpha' => 'Α',
+ 'zeta' => 'ζ',
+ 'Omega' => 'Ω',
+ 'nu' => 'ν',
+ 'sim' => '∼',
+ 'sect' => '\xa7',
+ 'phi' => 'φ',
+ 'sigmaf' => 'ς',
+ 'macr' => '\xaf',
+ 'minus' => '−',
+ 'Ccedil' => '\xc7',
+ 'ordm' => '\xba',
+ 'epsilon' => 'ε',
+ 'beta' => 'β',
+ 'rArr' => '⇒',
+ 'rho' => 'ρ',
+ 'aacute' => '\xe1',
+ 'eacute' => '\xe9',
+ 'omega' => 'ω',
+ 'middot' => '\xb7',
+ 'Gamma' => 'Γ',
+ 'Iacute' => '\xcd',
+ 'lang' => '〈',
+ 'spades' => '♠',
+ 'rsquo' => '’',
+ 'uml' => '\xa8',
+ 'thorn' => '\xfe',
+ 'ouml' => '\xf6',
+ 'thetasym' => 'ϑ',
+ 'or' => '∨',
+ 'raquo' => '\xbb',
+ 'acirc' => '\xe2',
+ 'ldquo' => '“',
+ 'hearts' => '♥',
+ 'sigma' => 'σ',
+ 'oacute' => '\xf3',
+=end
+ }
+end
module Irc
# miscellaneous useful functions
module Utils
- # read a time in string format, turn it into "seconds from now".
- # example formats handled are "5 minutes", "2 days", "five hours",
- # "11:30", "15:45:11", "one day", etc.
- #
- # Throws:: RunTimeError "invalid time string" on parse failure
- def Utils.timestr_offset(timestr)
- case timestr
- when (/^(\S+)\s+(\S+)$/)
- mult = $1
- unit = $2
- if(mult =~ /^([\d.]+)$/)
- num = $1.to_f
- raise "invalid time string" unless num
- else
- case mult
- when(/^(one|an|a)$/)
- num = 1
- when(/^two$/)
- num = 2
- when(/^three$/)
- num = 3
- when(/^four$/)
- num = 4
- when(/^five$/)
- num = 5
- when(/^six$/)
- num = 6
- when(/^seven$/)
- num = 7
- when(/^eight$/)
- num = 8
- when(/^nine$/)
- num = 9
- when(/^ten$/)
- num = 10
- when(/^fifteen$/)
- num = 15
- when(/^twenty$/)
- num = 20
- when(/^thirty$/)
- num = 30
- when(/^sixty$/)
- num = 60
- else
- raise "invalid time string"
- end
- end
- case unit
- when (/^(s|sec(ond)?s?)$/)
- return num
- when (/^(m|min(ute)?s?)$/)
- return num * 60
- when (/^(h|h(ou)?rs?)$/)
- return num * 60 * 60
- when (/^(d|days?)$/)
- return num * 60 * 60 * 24
- else
- raise "invalid time string"
- end
- when (/^(\d+):(\d+):(\d+)$/)
- hour = $1.to_i
- min = $2.to_i
- sec = $3.to_i
- now = Time.now
- later = Time.mktime(now.year, now.month, now.day, hour, min, sec)
- return later - now
- when (/^(\d+):(\d+)$/)
- hour = $1.to_i
- min = $2.to_i
- now = Time.now
- later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
- return later - now
- when (/^(\d+):(\d+)(am|pm)$/)
- hour = $1.to_i
- min = $2.to_i
- ampm = $3
- if ampm == "pm"
- hour += 12
- end
- now = Time.now
- later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
- return later - now
- when (/^(\S+)$/)
- num = 1
- unit = $1
- case unit
- when (/^(s|sec(ond)?s?)$/)
- return num
- when (/^(m|min(ute)?s?)$/)
- return num * 60
- when (/^(h|h(ou)?rs?)$/)
- return num * 60 * 60
- when (/^(d|days?)$/)
- return num * 60 * 60 * 24
- else
- raise "invalid time string"
- end
- else
- raise "invalid time string"
- end
- end
# turn a number of seconds into a human readable string, e.g
# 2 days, 3 hours, 18 minutes, 10 seconds
return ret
end
+
def Utils.safe_exec(command, *args)
IO.popen("-") {|p|
if(p)
$stderr = $stdout
exec(command, *args)
rescue Exception => e
- puts "exec of #{command} led to exception: #{e}"
+ puts "exec of #{command} led to exception: #{e.inspect}"
Kernel::exit! 0
end
puts "exec of #{command} failed"
}
rescue => e
# cheesy for now
- $stderr.puts "Utils.http_get exception: #{e}, while trying to get #{uristr}"
+ error "Utils.http_get exception: #{e.inspect}, while trying to get #{uristr}"
return nil
end
end
- # This is nasty-ass. I hate writing parsers.
- class Metar
- attr_reader :decoded
- attr_reader :input
- attr_reader :date
- attr_reader :nodata
- def initialize(string)
- str = nil
- @nodata = false
- string.each_line {|l|
- if str == nil
- # grab first line (date)
- @date = l.chomp.strip
- str = ""
- else
- if(str == "")
- str = l.chomp.strip
- else
- str += " " + l.chomp.strip
- end
+ def Utils.decode_html_entities(str)
+ if $we_have_html_entities_decoder
+ return HTMLEntities.decode_entities(str)
+ else
+ str.gsub(/(&(.+?);)/) {
+ symbol = $2
+ # remove the 0-paddng from unicode integers
+ if symbol =~ /#(.+)/
+ symbol = "##{$1.to_i.to_s}"
end
- }
- if @date && @date =~ /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+)$/
- # 2002/02/26 05:00
- @date = Time.gm($1, $2, $3, $4, $5, 0)
- else
- @date = Time.now
- end
- @input = str.chomp
- @cloud_layers = 0
- @cloud_coverage = {
- 'SKC' => '0',
- 'CLR' => '0',
- 'VV' => '8/8',
- 'FEW' => '1/8 - 2/8',
- 'SCT' => '3/8 - 4/8',
- 'BKN' => '5/8 - 7/8',
- 'OVC' => '8/8'
- }
- @wind_dir_texts = [
- 'North',
- 'North/Northeast',
- 'Northeast',
- 'East/Northeast',
- 'East',
- 'East/Southeast',
- 'Southeast',
- 'South/Southeast',
- 'South',
- 'South/Southwest',
- 'Southwest',
- 'West/Southwest',
- 'West',
- 'West/Northwest',
- 'Northwest',
- 'North/Northwest',
- 'North'
- ]
- @wind_dir_texts_short = [
- 'N',
- 'N/NE',
- 'NE',
- 'E/NE',
- 'E',
- 'E/SE',
- 'SE',
- 'S/SE',
- 'S',
- 'S/SW',
- 'SW',
- 'W/SW',
- 'W',
- 'W/NW',
- 'NW',
- 'N/NW',
- 'N'
- ]
- @weather_array = {
- 'MI' => 'Mild ',
- 'PR' => 'Partial ',
- 'BC' => 'Patches ',
- 'DR' => 'Low Drifting ',
- 'BL' => 'Blowing ',
- 'SH' => 'Shower(s) ',
- 'TS' => 'Thunderstorm ',
- 'FZ' => 'Freezing',
- 'DZ' => 'Drizzle ',
- 'RA' => 'Rain ',
- 'SN' => 'Snow ',
- 'SG' => 'Snow Grains ',
- 'IC' => 'Ice Crystals ',
- 'PE' => 'Ice Pellets ',
- 'GR' => 'Hail ',
- 'GS' => 'Small Hail and/or Snow Pellets ',
- 'UP' => 'Unknown ',
- 'BR' => 'Mist ',
- 'FG' => 'Fog ',
- 'FU' => 'Smoke ',
- 'VA' => 'Volcanic Ash ',
- 'DU' => 'Widespread Dust ',
- 'SA' => 'Sand ',
- 'HZ' => 'Haze ',
- 'PY' => 'Spray',
- 'PO' => 'Well-Developed Dust/Sand Whirls ',
- 'SQ' => 'Squalls ',
- 'FC' => 'Funnel Cloud Tornado Waterspout ',
- 'SS' => 'Sandstorm/Duststorm '
- }
- @cloud_condition_array = {
- 'SKC' => 'clear',
- 'CLR' => 'clear',
- 'VV' => 'vertical visibility',
- 'FEW' => 'a few',
- 'SCT' => 'scattered',
- 'BKN' => 'broken',
- 'OVC' => 'overcast'
- }
- @strings = {
- 'mm_inches' => '%s mm (%s inches)',
- 'precip_a_trace' => 'a trace',
- 'precip_there_was' => 'There was %s of precipitation ',
- 'sky_str_format1' => 'There were %s at a height of %s meters (%s feet)',
- 'sky_str_clear' => 'The sky was clear',
- 'sky_str_format2' => ', %s at a height of %s meter (%s feet) and %s at a height of %s meters (%s feet)',
- 'sky_str_format3' => ' and %s at a height of %s meters (%s feet)',
- 'clouds' => ' clouds',
- 'clouds_cb' => ' cumulonimbus clouds',
- 'clouds_tcu' => ' towering cumulus clouds',
- 'visibility_format' => 'The visibility was %s kilometers (%s miles).',
- 'wind_str_format1' => 'blowing at a speed of %s meters per second (%s miles per hour)',
- 'wind_str_format2' => ', with gusts to %s meters per second (%s miles per hour),',
- 'wind_str_format3' => ' from the %s',
- 'wind_str_calm' => 'calm',
- 'precip_last_hour' => 'in the last hour. ',
- 'precip_last_6_hours' => 'in the last 3 to 6 hours. ',
- 'precip_last_24_hours' => 'in the last 24 hours. ',
- 'precip_snow' => 'There is %s mm (%s inches) of snow on the ground. ',
- 'temp_min_max_6_hours' => 'The maximum and minimum temperatures over the last 6 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit).',
- 'temp_max_6_hours' => 'The maximum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
- 'temp_min_6_hours' => 'The minimum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
- 'temp_min_max_24_hours' => 'The maximum and minimum temperatures over the last 24 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit). ',
- 'light' => 'Light ',
- 'moderate' => 'Moderate ',
- 'heavy' => 'Heavy ',
- 'mild' => 'Mild ',
- 'nearby' => 'Nearby ',
- 'current_weather' => 'Current weather is %s. ',
- 'pretty_print_metar' => '%s on %s, the wind was %s at %s. The temperature was %s degrees Celsius (%s degrees Fahrenheit), and the pressure was %s hPa (%s inHg). The relative humidity was %s%%. %s %s %s %s %s'
- }
-
- parse
- end
-
- def store_speed(value, windunit, meterspersec, knots, milesperhour)
- # Helper function to convert and store speed based on unit.
- # &$meterspersec, &$knots and &$milesperhour are passed on
- # reference
- if (windunit == 'KT')
- # The windspeed measured in knots:
- @decoded[knots] = sprintf("%.2f", value)
- # The windspeed measured in meters per second, rounded to one decimal place:
- @decoded[meterspersec] = sprintf("%.2f", value.to_f * 0.51444)
- # The windspeed measured in miles per hour, rounded to one decimal place: */
- @decoded[milesperhour] = sprintf("%.2f", value.to_f * 1.1507695060844667)
- elsif (windunit == 'MPS')
- # The windspeed measured in meters per second:
- @decoded[meterspersec] = sprintf("%.2f", value)
- # The windspeed measured in knots, rounded to one decimal place:
- @decoded[knots] = sprintf("%.2f", value.to_f / 0.51444)
- #The windspeed measured in miles per hour, rounded to one decimal place:
- @decoded[milesperhour] = sprintf("%.1f", value.to_f / 0.51444 * 1.1507695060844667)
- elsif (windunit == 'KMH')
- # The windspeed measured in kilometers per hour:
- @decoded[meterspersec] = sprintf("%.1f", value.to_f * 1000 / 3600)
- @decoded[knots] = sprintf("%.1f", value.to_f * 1000 / 3600 / 0.51444)
- # The windspeed measured in miles per hour, rounded to one decimal place:
- @decoded[milesperhour] = sprintf("%.1f", knots.to_f * 1.1507695060844667)
- end
- end
-
- def parse
- @decoded = Hash.new
- puts @input
- @input.split(" ").each {|part|
- if (part == 'METAR')
- # Type of Report: METAR
- @decoded['type'] = 'METAR'
- elsif (part == 'SPECI')
- # Type of Report: SPECI
- @decoded['type'] = 'SPECI'
- elsif (part == 'AUTO')
- # Report Modifier: AUTO
- @decoded['report_mod'] = 'AUTO'
- elsif (part == 'NIL')
- @nodata = true
- elsif (part =~ /^\S{4}$/ && ! (@decoded.has_key?('station')))
- # Station Identifier
- @decoded['station'] = part
- elsif (part =~ /([0-9]{2})([0-9]{2})([0-9]{2})Z/)
- # ignore this bit, it's useless without month/year. some of these
- # things are hideously out of date.
- # now = Time.new
- # time = Time.gm(now.year, now.month, $1, $2, $3, 0)
- # Date and Time of Report
- # @decoded['time'] = time
- elsif (part == 'COR')
- # Report Modifier: COR
- @decoded['report_mod'] = 'COR'
- elsif (part =~ /([0-9]{3}|VRB)([0-9]{2,3}).*(KT|MPS|KMH)/)
- # Wind Group
- windunit = $3
- # now do ereg to get the actual values
- part =~ /([0-9]{3}|VRB)([0-9]{2,3})((G[0-9]{2,3})?#{windunit})/
- if ($1 == 'VRB')
- @decoded['wind_deg'] = 'variable directions'
- @decoded['wind_dir_text'] = 'variable directions'
- @decoded['wind_dir_text_short'] = 'VAR'
- else
- @decoded['wind_deg'] = $1
- @decoded['wind_dir_text'] = @wind_dir_texts[($1.to_i/22.5).round]
- @decoded['wind_dir_text_short'] = @wind_dir_texts_short[($1.to_i/22.5).round]
- end
- store_speed($2, windunit,
- 'wind_meters_per_second',
- 'wind_knots',
- 'wind_miles_per_hour')
- if ($4 != nil)
- # We have a report with information about the gust.
- # First we have the gust measured in knots
- if ($4 =~ /G([0-9]{2,3})/)
- store_speed($1,windunit,
- 'wind_gust_meters_per_second',
- 'wind_gust_knots',
- 'wind_gust_miles_per_hour')
- end
- end
- elsif (part =~ /([0-9]{3})V([0-9]{3})/)
- # Variable wind-direction
- @decoded['wind_var_beg'] = $1
- @decoded['wind_var_end'] = $2
- elsif (part == "9999")
- # A strange value. When you look at other pages you see it
- # interpreted like this (where I use > to signify 'Greater
- # than'):
- @decoded['visibility_miles'] = '>7';
- @decoded['visibility_km'] = '>11.3';
- elsif (part =~ /^([0-9]{4})$/)
- # Visibility in meters (4 digits only)
- # The visibility measured in kilometers, rounded to one decimal place.
- @decoded['visibility_km'] = sprintf("%.1f", $1.to_i / 1000)
- # The visibility measured in miles, rounded to one decimal place.
- @decoded['visibility_miles'] = sprintf("%.1f", $1.to_i / 1000 / 1.609344)
- elsif (part =~ /^[0-9]$/)
- # Temp Visibility Group, single digit followed by space
- @decoded['temp_visibility_miles'] = part
- elsif (@decoded['temp_visibility_miles'] && (@decoded['temp_visibility_miles']+' '+part) =~ /^M?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/)
- # Visibility Group
- if ($4 == '/')
- vis_miles = $2.to_i + $3.to_i/$5.to_i
- else
- vis_miles = $1.to_i;
- end
- if (@decoded['temp_visibility_miles'][0] == 'M')
- # The visibility measured in miles, prefixed with < to indicate 'Less than'
- @decoded['visibility_miles'] = '<' + sprintf("%.1f", vis_miles)
- # The visibility measured in kilometers. The value is rounded
- # to one decimal place, prefixed with < to indicate 'Less than' */
- @decoded['visibility_km'] = '<' . sprintf("%.1f", vis_miles * 1.609344)
- else
- # The visibility measured in mile.s */
- @decoded['visibility_miles'] = sprintf("%.1f", vis_miles)
- # The visibility measured in kilometers, rounded to one decimal place.
- @decoded['visibility_km'] = sprintf("%.1f", vis_miles * 1.609344)
- end
- elsif (part =~ /^(-|\+|VC|MI)?(TS|SH|FZ|BL|DR|BC|PR|RA|DZ|SN|SG|GR|GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$/)
- # Current weather-group
- @decoded['weather'] = '' unless @decoded.has_key?('weather')
- if (part[0].chr == '-')
- # A light phenomenon
- @decoded['weather'] += @strings['light']
- part = part[1,part.length]
- elsif (part[0].chr == '+')
- # A heavy phenomenon
- @decoded['weather'] += @strings['heavy']
- part = part[1,part.length]
- elsif (part[0,2] == 'VC')
- # Proximity Qualifier
- @decoded['weather'] += @strings['nearby']
- part = part[2,part.length]
- elsif (part[0,2] == 'MI')
- @decoded['weather'] += @strings['mild']
- part = part[2,part.length]
- else
- # no intensity code => moderate phenomenon
- @decoded['weather'] += @strings['moderate']
- end
-
- while (part && bite = part[0,2]) do
- # Now we take the first two letters and determine what they
- # mean. We append this to the variable so that we gradually
- # build up a phrase.
-
- @decoded['weather'] += @weather_array[bite]
- # Here we chop off the two first letters, so that we can take
- # a new bite at top of the while-loop.
- part = part[2,-1]
- end
- elsif (part =~ /(SKC|CLR)/)
- # Cloud-layer-group.
- # There can be up to three of these groups, so we store them as
- # cloud_layer1, cloud_layer2 and cloud_layer3.
-
- @cloud_layers += 1;
- # Again we have to translate the code-characters to a
- # meaningful string.
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] = @cloud_condition_array[$1]
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
- elsif (part =~ /^(VV|FEW|SCT|BKN|OVC)([0-9]{3})(CB|TCU)?$/)
- # We have found (another) a cloud-layer-group. There can be up
- # to three of these groups, so we store them as cloud_layer1,
- # cloud_layer2 and cloud_layer3.
- @cloud_layers += 1;
- # Again we have to translate the code-characters to a meaningful string.
- if ($3 == 'CB')
- # cumulonimbus (CB) clouds were observed. */
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
- @cloud_condition_array[$1] + @strings['clouds_cb']
- elsif ($3 == 'TCU')
- # towering cumulus (TCU) clouds were observed.
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
- @cloud_condition_array[$1] + @strings['clouds_tcu']
- else
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
- @cloud_condition_array[$1] + @strings['clouds']
- end
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_ft'] = $2.to_i * 100
- @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_m'] = ($2.to_f * 30.48).round
- elsif (part =~ /^T([0-9]{4})$/)
- store_temp($1,'temp_c','temp_f')
- elsif (part =~ /^T?(M?[0-9]{2})\/(M?[0-9\/]{1,2})?$/)
- # Temperature/Dew Point Group
- # The temperature and dew-point measured in Celsius.
- @decoded['temp_c'] = sprintf("%d", $1.tr('M', '-'))
- if $2 == "//" || !$2
- @decoded['dew_c'] = 0
- else
- @decoded['dew_c'] = sprintf("%.1f", $2.tr('M', '-'))
- end
- # The temperature and dew-point measured in Fahrenheit, rounded to
- # the nearest degree.
- @decoded['temp_f'] = ((@decoded['temp_c'].to_f * 9 / 5) + 32).round
- @decoded['dew_f'] = ((@decoded['dew_c'].to_f * 9 / 5) + 32).round
- elsif(part =~ /A([0-9]{4})/)
- # Altimeter
- # The pressure measured in inHg
- @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_i/100)
- # The pressure measured in mmHg, hPa and atm
- @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.254)
- @decoded['altimeter_hpa'] = sprintf("%d", ($1.to_f * 0.33863881578947).to_i)
- @decoded['altimeter_atm'] = sprintf("%.3f", $1.to_f * 3.3421052631579e-4)
- elsif(part =~ /Q([0-9]{4})/)
- # Altimeter
- # This is strange, the specification doesnt say anything about
- # the Qxxxx-form, but it's in the METARs.
- # The pressure measured in hPa
- @decoded['altimeter_hpa'] = sprintf("%d", $1.to_i)
- # The pressure measured in mmHg, inHg and atm
- @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.7500616827)
- @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_f * 0.0295299875)
- @decoded['altimeter_atm'] = sprintf("%.3f", $1.to_f * 9.869232667e-4)
- elsif (part =~ /^T([0-9]{4})([0-9]{4})/)
- # Temperature/Dew Point Group, coded to tenth of degree.
- # The temperature and dew-point measured in Celsius.
- store_temp($1,'temp_c','temp_f')
- store_temp($2,'dew_c','dew_f')
- elsif (part =~ /^1([0-9]{4}$)/)
- # 6 hour maximum temperature Celsius, coded to tenth of degree
- store_temp($1,'temp_max6h_c','temp_max6h_f')
- elsif (part =~ /^2([0-9]{4}$)/)
- # 6 hour minimum temperature Celsius, coded to tenth of degree
- store_temp($1,'temp_min6h_c','temp_min6h_f')
- elsif (part =~ /^4([0-9]{4})([0-9]{4})$/)
- # 24 hour maximum and minimum temperature Celsius, coded to
- # tenth of degree
- store_temp($1,'temp_max24h_c','temp_max24h_f')
- store_temp($2,'temp_min24h_c','temp_min24h_f')
- elsif (part =~ /^P([0-9]{4})/)
- # Precipitation during last hour in hundredths of an inch
- # (store as inches)
- @decoded['precip_in'] = sprintf("%.2f", $1.to_f/100)
- @decoded['precip_mm'] = sprintf("%.2f", $1.to_f * 0.254)
- elsif (part =~ /^6([0-9]{4})/)
- # Precipitation during last 3 or 6 hours in hundredths of an
- # inch (store as inches)
- @decoded['precip_6h_in'] = sprintf("%.2f", $1.to_f/100)
- @decoded['precip_6h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
- elsif (part =~ /^7([0-9]{4})/)
- # Precipitation during last 24 hours in hundredths of an inch
- # (store as inches)
- @decoded['precip_24h_in'] = sprintf("%.2f", $1.to_f/100)
- @decoded['precip_24h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
- elsif(part =~ /^4\/([0-9]{3})/)
- # Snow depth in inches
- @decoded['snow_in'] = sprintf("%.2f", $1);
- @decoded['snow_mm'] = sprintf("%.2f", $1.to_f * 25.4)
- else
- # If we couldn't match the group, we assume that it was a
- # remark.
- @decoded['remarks'] = '' unless @decoded.has_key?("remarks")
- @decoded['remarks'] += ' ' + part;
- end
+ # output the symbol's irc-translated character, or a * if it's unknown
+ UNESCAPE_TABLE[symbol] || '*'
}
-
- # Relative humidity
- # p @decoded['dew_c'] # 11.0
- # p @decoded['temp_c'] # 21.0
- # => 56.1
- @decoded['rel_humidity'] = sprintf("%.1f",100 *
- (6.11 * (10.0**(7.5 * @decoded['dew_c'].to_f / (237.7 + @decoded['dew_c'].to_f)))) / (6.11 * (10.0 ** (7.5 * @decoded['temp_c'].to_f / (237.7 + @decoded['temp_c'].to_f))))) if @decoded.has_key?('dew_c')
- end
-
- def store_temp(temp,temp_cname,temp_fname)
- # Given a numerical temperature temp in Celsius, coded to tenth of
- # degree, store in @decoded[temp_cname], convert to Fahrenheit
- # and store in @decoded[temp_fname]
- # Note: temp is converted to negative if temp > 100.0 (See
- # Federal Meteorological Handbook for groups T, 1, 2 and 4)
-
- # Temperature measured in Celsius, coded to tenth of degree
- temp = temp.to_f/10
- if (temp >100.0)
- # first digit = 1 means minus temperature
- temp = -(temp - 100.0)
- end
- @decoded[temp_cname] = sprintf("%.1f", temp)
- # The temperature in Fahrenheit.
- @decoded[temp_fname] = sprintf("%.1f", (temp * 9 / 5) + 32)
end
-
- def pretty_print_precip(precip_mm, precip_in)
- # Returns amount if $precip_mm > 0, otherwise "trace" (see Federal
- # Meteorological Handbook No. 1 for code groups P, 6 and 7) used in
- # several places, so standardized in one function.
- if (precip_mm.to_i > 0)
- amount = sprintf(@strings['mm_inches'], precip_mm, precip_in)
- else
- amount = @strings['a_trace']
- end
- return sprintf(@strings['precip_there_was'], amount)
- end
-
- def pretty_print
- if @nodata
- return "The weather stored for #{@decoded['station']} consists of the string 'NIL' :("
- end
-
- ["temp_c", "altimeter_hpa"].each {|key|
- if !@decoded.has_key?(key)
- return "The weather stored for #{@decoded['station']} could not be parsed (#{@input})"
- end
- }
-
- mins_old = ((Time.now - @date.to_i).to_f/60).round
- if (mins_old <= 60)
- weather_age = mins_old.to_s + " minutes ago,"
- elsif (mins_old <= 60 * 25)
- weather_age = (mins_old / 60).to_s + " hours, "
- weather_age += (mins_old % 60).to_s + " minutes ago,"
- else
- # return "The weather stored for #{@decoded['station']} is hideously out of date :( (Last update #{@date})"
- weather_age = "The weather stored for #{@decoded['station']} is hideously out of date :( here it is anyway:"
- end
-
- if(@decoded.has_key?("cloud_layer1_altitude_ft"))
- sky_str = sprintf(@strings['sky_str_format1'],
- @decoded["cloud_layer1_condition"],
- @decoded["cloud_layer1_altitude_m"],
- @decoded["cloud_layer1_altitude_ft"])
- else
- sky_str = @strings['sky_str_clear']
- end
-
- if(@decoded.has_key?("cloud_layer2_altitude_ft"))
- if(@decoded.has_key?("cloud_layer3_altitude_ft"))
- sky_str += sprintf(@strings['sky_str_format2'],
- @decoded["cloud_layer2_condition"],
- @decoded["cloud_layer2_altitude_m"],
- @decoded["cloud_layer2_altitude_ft"],
- @decoded["cloud_layer3_condition"],
- @decoded["cloud_layer3_altitude_m"],
- @decoded["cloud_layer3_altitude_ft"])
- else
- sky_str += sprintf(@strings['sky_str_format3'],
- @decoded["cloud_layer2_condition"],
- @decoded["cloud_layer2_altitude_m"],
- @decoded["cloud_layer2_altitude_ft"])
- end
- end
- sky_str += "."
-
- if(@decoded.has_key?("visibility_miles"))
- visibility = sprintf(@strings['visibility_format'],
- @decoded["visibility_km"],
- @decoded["visibility_miles"])
- else
- visibility = ""
- end
-
- if (@decoded.has_key?("wind_meters_per_second") && @decoded["wind_meters_per_second"].to_i > 0)
- wind_str = sprintf(@strings['wind_str_format1'],
- @decoded["wind_meters_per_second"],
- @decoded["wind_miles_per_hour"])
- if (@decoded.has_key?("wind_gust_meters_per_second") && @decoded["wind_gust_meters_per_second"].to_i > 0)
- wind_str += sprintf(@strings['wind_str_format2'],
- @decoded["wind_gust_meters_per_second"],
- @decoded["wind_gust_miles_per_hour"])
- end
- wind_str += sprintf(@strings['wind_str_format3'],
- @decoded["wind_dir_text"])
- else
- wind_str = @strings['wind_str_calm']
- end
-
- prec_str = ""
- if (@decoded.has_key?("precip_in"))
- prec_str += pretty_print_precip(@decoded["precip_mm"], @decoded["precip_in"]) + @strings['precip_last_hour']
- end
- if (@decoded.has_key?("precip_6h_in"))
- prec_str += pretty_print_precip(@decoded["precip_6h_mm"], @decoded["precip_6h_in"]) + @strings['precip_last_6_hours']
- end
- if (@decoded.has_key?("precip_24h_in"))
- prec_str += pretty_print_precip(@decoded["precip_24h_mm"], @decoded["precip_24h_in"]) + @strings['precip_last_24_hours']
- end
- if (@decoded.has_key?("snow_in"))
- prec_str += sprintf(@strings['precip_snow'], @decoded["snow_mm"], @decoded["snow_in"])
- end
-
- temp_str = ""
- if (@decoded.has_key?("temp_max6h_c") && @decoded.has_key?("temp_min6h_c"))
- temp_str += sprintf(@strings['temp_min_max_6_hours'],
- @decoded["temp_max6h_c"],
- @decoded["temp_min6h_c"],
- @decoded["temp_max6h_f"],
- @decoded["temp_min6h_f"])
- else
- if (@decoded.has_key?("temp_max6h_c"))
- temp_str += sprintf(@strings['temp_max_6_hours'],
- @decoded["temp_max6h_c"],
- @decoded["temp_max6h_f"])
- end
- if (@decoded.has_key?("temp_min6h_c"))
- temp_str += sprintf(@strings['temp_max_6_hours'],
- @decoded["temp_min6h_c"],
- @decoded["temp_min6h_f"])
- end
- end
- if (@decoded.has_key?("temp_max24h_c"))
- temp_str += sprintf(@strings['temp_min_max_24_hours'],
- @decoded["temp_max24h_c"],
- @decoded["temp_min24h_c"],
- @decoded["temp_max24h_f"],
- @decoded["temp_min24h_f"])
- end
-
- if (@decoded.has_key?("weather"))
- weather_str = sprintf(@strings['current_weather'], @decoded["weather"])
- else
- weather_str = ''
- end
-
- return sprintf(@strings['pretty_print_metar'],
- weather_age,
- @date,
- wind_str, @decoded["station"], @decoded["temp_c"],
- @decoded["temp_f"], @decoded["altimeter_hpa"],
- @decoded["altimeter_inhg"],
- @decoded["rel_humidity"], sky_str,
- visibility, weather_str, prec_str, temp_str).strip
- end
-
- def to_s
- @input
- end
- end
-
- def Utils.get_metar(station)
- station.upcase!
-
- result = Utils.http_get("http://weather.noaa.gov/pub/data/observations/metar/stations/#{station}.TXT")
- return nil unless result
- return Metar.new(result)
end
end
end