#!/usr/bin/env ruby # == Barometer # This is the command line interface to the barometer gem. # # == Examples # This command will measure the weather for the given query. # barometer berlin # # Other examples: # barometer --yahoo 90210 # barometer --verbose 'new york' # # == Local Web Demo # You can easily interact directly with barometer with the command: # barometer -w # # This demo has 2 gem requirements: # - sinatra (tested with 0.9.1.1) # - vegas (tested with 0.0.1) # # == Usage # barometer [options] query # # For help use: barometer -h # # Options: # -v, --version Display the version, then exit # -V, --verbose Verbose output # -t, --timeout seconds until service queries will timeout # -g, --geocode Force Geocoding of query # -z, --timezone Enhance Timezone # -m, --metric measure in metric # -i, --imperial measure in imperial # --wunderground add wunderground as a source # --yahoo add yahoo as a source # --google add google as a source # --weather add weather.com as a source # --bug add weather_bug as a source # -p, --pop pop threshold used to determine wet? # -s, --wind wind speed threshold used to determine windy? # -a, --at time/date used to determine when to calculate summary # # Web Demo: # -w, --web run web-app with barometer demo # -k, --kill stop the web demo background process # -S, --status show the web demo status # # == Author # Mark G # http://github.com/attack/barometer # # == Copyright # Copyright (c) 2009 Mark G. Licensed under the MIT License: # http://www.opensource.org/licenses/mit-license.php require 'rubygems' require 'barometer' require 'optparse' require 'rdoc/usage' require 'ostruct' require 'time' require 'date' require 'yaml' # file where API keys are stored KEY_FILE = File.expand_path(File.join('~', '.barometer')) BAROMETER_VERSION = '0.6.2' class App attr_reader :options def initialize(arguments, stdin) @arguments = arguments.dup # Set defaults @options = OpenStruct.new @options.timeout = 15 @options.geocode = false @options.timezone = false @options.metric = true @options.sources = [] @options.verbode = false @options.web = false @options.at = nil @options.default = true # thresholds @options.windy_m = 10 @options.windy_i = 7 @options.pop = 50 end # Parse options, check arguments, then process the command def run if parsed_options? && arguments_valid? puts "Start at #{DateTime.now}\n\n" if @options.verbose output_options if @options.verbose # [Optional] process_arguments process_command puts "\nFinished at #{DateTime.now}" if @options.verbose else output_usage end end protected # future options # # time: -a --at # def parsed_options? # Specify options opt = OptionParser.new opt.on('-v', '--version') { output_version ; exit 0 } opt.on('-h', '--help') { output_help } opt.on('-V', '--verbose') { @options.verbose = true } opt.on('-a n', '--at n') {|n| @options.at = Time.parse(n.to_s) } opt.on('-t n', '--timeout n') {|n| @options.timeout = n } opt.on('-g', '--geocode') { @options.geocode = true } opt.on('-z', '--timezone') { @options.timezone = true } opt.on('-m', '--metric') { @options.metric = true } opt.on('-i', '--imperial') { @options.metric = false } opt.on('--wunderground') { @options.sources << :wunderground; @options.default = false } opt.on('--yahoo') { @options.sources << :yahoo; @options.default = false } opt.on('--google') { @options.sources << :google; @options.default = false } opt.on('--weather') { @options.sources << :weather_dot_com; @options.default = false } opt.on('--bug') { @options.sources << :weather_bug; @options.default = false } opt.on('-p n', '--pop n') {|n| @options.pop = n.to_i || 50 } opt.on('-s n', '--wind n') {|n| @options.metric ? @options.windy_m = n.to_f || 10 : @options.windy_i = n.to_f || 7 } # pass these onto vegas opt.on('-w', '--web') { @options.web = true; ARGV.shift } opt.on('-k', '--kill') { @options.web = true } opt.on('-S', '--status') { @options.web = true } opt.parse!(@arguments) rescue return false process_options true end def config_weather_dot_com if File.exists?(KEY_FILE) keys = YAML.load_file(KEY_FILE) if keys["weather"] && keys["weather"]["partner"] && keys["weather"]["license"] partner_key = keys["weather"]["partner"].to_s license_key = keys["weather"]["license"].to_s else weather_key_message exit end else File.open(KEY_FILE, 'w') {|f| f << "\nweather:\n partner: PARTNER_KEY\n license: LICENSE_KEY" } weather_key_message exit end { :weather_dot_com => { :keys => { :partner => partner_key, :license => license_key } } } end def config_weather_bug if File.exists?(KEY_FILE) keys = YAML.load_file(KEY_FILE) if keys["weather_bug"] && keys["weather_bug"]["code"] code = keys["weather_bug"]["code"].to_s else bug_key_message exit end else File.open(KEY_FILE, 'w') {|f| f << "\nweather_bug:\n code: API_CODE" } bug_key_message exit end { :weather_bug => { :keys => { :code => code } } } end # Performs post-parse processing on options def process_options @options.sources << :wunderground if @options.default @options.sources = @options.sources.uniq Barometer.force_geocode = @options.geocode Barometer.enhance_timezone = @options.timezone if @options.sources.include?(:weather_dot_com) @options.sources.delete(:weather_dot_com) @options.sources << config_weather_dot_com end if @options.sources.include?(:weather_bug) @options.sources.delete(:weather_bug) @options.sources << config_weather_bug end Barometer.config = { 1 => @options.sources } Barometer.timeout = @options.timeout end def output_options puts "Options:\n" @options.marshal_dump.each do |name, val| puts " #{name} = #{val}" end puts end # True if required arguments were provided def arguments_valid? true if (@arguments.length >= 1 || @options.web) end # Setup the arguments def process_arguments #puts @arguments.inspect end def output_help output_version #output_usage end def output_usage puts "Usage: " puts "barometer [options] query" puts puts "For help use: barometer -h" puts puts "options:" puts " -v, --version Display the version, then exit" puts " -V, --verbose Verbose output" puts " -t, --timeout seconds until service queries will timeout" puts " -g, --geocode Force Geocoding of query" puts " -z, --timezone Force timezone query" puts " -m, --metric measure in metric" puts " -i, --imperial measure in imperial" puts " --wunderground add wunderground as a source" puts " --yahoo add yahoo as a source" puts " --google add google as a source" puts " --weather add weather.com as a source" puts " --bug add weather_bug as a source" puts " -p, --pop pop threshold used to determine wet?" puts " -s, --wind wind speed threshold used to determine windy?" puts " -a, --at time/date used to determine when to calculate summary" puts puts " Web Demo:" puts " -w, --web run web-app with barometer demo" puts " -k, --kill stop the web demo background process" puts " -S, --status show the web demo status" end def output_version puts "#{File.basename(__FILE__)} version #{BAROMETER_VERSION}" end def process_command if @options.web run_web_mode(@arguments.join(" ")) else barometer = Barometer.new(@arguments.join(" ")) begin if @options.verbose Barometer::debug! div(char="*") puts "DEBUG LOG" blank end barometer.measure(@options.metric) if barometer blank if @options.verbose pretty_output(barometer) if barometer.weather rescue Barometer::OutOfSources puts puts " SORRY: your query did not provide any results" puts end end end end # # HELPERS # @level = 1 def y(value) value ? "yes" : "no" end def div(char="#") puts char*50 end def title(title, level=1) @level = level puts "#{" " * @level}-- #{title} --" end def value(title, value) puts "#{" " * @level}#{title}: #{value}" unless value.nil? end def blank puts end def section(title, level=1, show_blank=true) @level = level title(title, level); yield; blank if show_blank end def pretty_hash(hash) return unless hash.is_a?(Hash) hash.each { |k,v| value(k,v) } end def pretty_summary(s) return unless s section("AVERAGES") do pretty_hash({ "humidity" => s.humidity.to_i, "temperature" => s.temperature, "wind" => s.wind, "pressure" => s.pressure, "dew_point" => s.dew_point, "heat_index" => s.heat_index, "wind_chill" => s.wind_chill, "visibility" => s.visibility }) end section("SUMMARY#{ " (@ #{@options.at})" if @options.at }") do pretty_hash({ "day?" => s.day?(@options.at), "sunny?" => s.sunny?(@options.at), "windy?" => s.windy?(@options.at, @options.metric ? @options.windy_m : @options.windy_i), "wet?" => s.wet?(@options.at, @options.pop) }) end end def pretty_query(q) return unless q section("ORIGINAL QUERY", 1) do pretty_hash({ "Query" => q.q, "Format" => q.format, "Country Code" => q.country_code }) end if q.geo section("GEO", 2) do pretty_hash({ "Address" => q.geo.address, "Query" => q.geo.query, "Locality" => q.geo.locality, "Region" => q.geo.region, "Country" => q.geo.country, "Country Code" => q.geo.country_code, "Latitude" => q.geo.latitude, "Longitude" => q.geo.longitude }) end end end def pretty_location(l) return unless l section("LOCATION", 2) do pretty_hash({ "ID" => l.id, "Name" => l.name, "City" => l.city, "State Name" => l.state_name, "State Code" => l.state_code, "Country" => l.country, "Country Code" => l.country_code, "Zip Code" => l.zip_code, "Latitude" => l.latitude, "Longitude" => l.longitude }) end end def pretty_station(s) return unless s section("STATION", 2) do pretty_hash({ "ID" => s.id, "Name" => s.name, "City" => s.city, "State Name" => s.state_name, "State Code" => s.state_code, "Country" => s.country, "Country Code" => s.country_code, "Zip Code" => s.zip_code, "Latitude" => s.latitude, "Longitude" => s.longitude }) end end def pretty_timezone(t) return unless t section("TIMEZONE", 2) do pretty_hash({ "Long" => t.full, "Code" => t.code, "DST?" => t.dst?, "Now" => t.now(true), "Today" => t.today }) end end def pretty_current(c) return unless c section("CURRENT", 2) do pretty_hash({ "Measured at" => c.current_at, "Updated at" => c.updated_at, "Humidity" => c.humidity, "Icon" => c.icon, "Condition" => c.condition, "Temperature" => c.temperature, "Dew Point" => c.dew_point, "Heat Index" => c.heat_index, "Pressure" => c.pressure, "Visibility" => c.visibility, "Wind Chill" => c.wind_chill }) pretty_hash({ "Wind" => c.wind, "Wind Direction" => c.wind.direction, "Wind Degrees" => c.wind.degrees }) if c.wind pretty_hash({ "Sun Rise" => c.sun.rise, "Sun Set" => c.sun.set }) if c.sun end end def pretty_forecast(f) return unless f section("FOR: #{f.date}", 3) do pretty_hash({ "Valid From" => f.valid_start_date.to_s(true), "Valid Until" => f.valid_end_date.to_s(true), "Icon" => f.icon, "Description" => f.description, "Condition" => f.condition, "High" => f.high, "Low" => f.low, "POP" => f.pop, "Humidity" => f.humidity }) pretty_hash({ "Wind" => f.wind, "Wind Direction" => f.wind.direction, "Wind Degrees" => f.wind.degrees }) if f.wind pretty_hash({ "Sun Rise" => f.sun.rise, "Sun Set" => f.sun.set }) if f.sun end end def pretty_forecasts(forecasts) return unless forecasts section("FORECAST(s)", 3, false) do blank forecasts.each do |forecast| pretty_forecast(forecast) end end end def pretty_measurement(m) return unless m section(m.source.to_s, 1) do pretty_hash({ "Measured At" => m.measured_at, "Source" => m.source, "Time Stamp" => m.utc_time_stamp, "Metric" => m.metric?, "Success" => m.success?, "Service Time" => "#{(m.end_at - m.start_at)} s" }) end section("MODIFIED QUERY", 2) do pretty_hash({ "Query" => m.query, "Format" => m.format }) end pretty_location(m.location) pretty_station(m.station) pretty_timezone(m.timezone) pretty_current(m.current) pretty_forecasts(m.forecast) end def pretty_measurements(w) return unless w section("MEASUREMENTS", 1) do blank w.measurements.each do |m| pretty_measurement(m) end end end def pretty_info(w) title("INFO", 1) value("GitHub", "http://github.com/attack/barometer") value("Barometer Version", BAROMETER_VERSION) value("Total Time", "#{(w.end_at - w.start_at)} s") end def pretty_output(barometer) weather = barometer.weather if weather div puts "#" puts "# #{weather.default.location.name || barometer.query.q}" puts "#" div blank pretty_summary(weather) pretty_query(barometer.query) pretty_measurements(weather) pretty_info(weather) div("-") end end def run_web_mode(query=nil) require 'rubygems' require File.expand_path(File.dirname(__FILE__) + '/../lib/demometer/demometer.rb') require 'vegas' Vegas::Runner.new(Demometer, 'demometer') do |opts, app| # opts is an option parser object # app is your app class end end def geocode_google_key_message puts puts "MISSING KEYS !!!" puts "Please update the key_file '#{KEY_FILE}' with your google api key" puts "Get it here: http://code.google.com/apis/maps/signup.html" puts "Then, add this line to the file:" puts "google:" puts " geocode: YOUR_KEY_KERE" puts end def weather_key_message puts puts "MISSING KEYS !!!" puts "Please update the key_file '#{KEY_FILE}' with your weather api keys" puts "Get it here: ???" puts "Then, add these lines to the file:" puts "weather:" puts " partner: PARTNER_KEY" puts " license: LICENSE_KEY" puts end def bug_key_message puts puts "MISSING KEYS !!!" puts "Please update the key_file '#{KEY_FILE}' with your weather_bug api key" puts "Get it here: ???" puts "Then, add these lines to the file:" puts "weather_bug:" puts " code: API_CODE" puts end # set API keys if File.exists?(KEY_FILE) keys = YAML.load_file(KEY_FILE) if keys["google"] && keys["google"]["geocode"] Barometer.google_geocode_key = keys["google"]["geocode"] else geocode_google_key_message exit end else File.open(KEY_FILE, 'w') {|f| f << "google: geocode: YOUR_KEY_KERE" } geocode_google_key_message exit end # Create and run the application app = App.new(ARGV, STDIN) app.run