#!/usr/bin/ruby -w
#
# $Id: GetQuote2,v 1.1 2004/07/09 09:44:56 ianmacd Exp $
#
# This is a drop-in replacement for the GetQuote2 script that is part of
# GKrellStock, a GKrellM stock plug-in. Using this version of GetQuote2
# instead of the original will allow you to use GKrellStock without requiring
# the installation of the Perl Finance::Quote module.
#
# This version of GetQuote2 is a rather clumsy first-pass Ruby port of the
# original. It was written more as a proof-of-concept for Ruby/Finance than
# anything else. Some of the comments even still refer to the old Perl code.
#
# The original GetQuote2 was written by M.R. Muthu Kumar
# <m_muthukumar@users.sourceforge.net>, with the help of getstockquote.pl by
# William Rhodes <wrhodes@27.org>.
#
# Find out more about GKrellStock at:
#
#  http://gkrellstock.sourceforge.net/


require 'etc'
require 'finance/quote'

# You have to specify a valid quote source and at least one ticker symbol.
#
def check_input( input )

  src_name = src_val = src_err = src_found = ''

  ticker_src = {
    'asia'	      => 'Asian Markets',
    'australia'	      => 'Australian SE',
    'canada'	      => 'Canadian Markets',
    'dwsfunds'	      => 'Deutsche Bank Gruppe Funds',
    'europe'	      => 'European Markets',
    'nasdaq'	      => 'NASDAQ',
    'nyse'	      => 'NYSE',
    'tiaacref'	      => 'TIAA-CREF',
    'troweprice'      => 'Quotes from T. Rowe Price',
    'uk_unit_trusts'  => 'UK Unit Trusts',
    'usa'	      => 'USA Markets',
    'vanguard'	      => 'Quotes from Vanguard Group',
    'vwd'	      => 'Vereinigte Wirtschaftsdienste GmbH'
  }

  # Check for verbose, shift @input anyway
  #
  if input[0] =~ /\-\-verbose=yes/i
    verbose = true
    input.shift
  elsif input[0] =~ /\-\-verbose=no/i
    verbose = false
    input.shift
  end

  # Check for text, shift @input anyway
  #
  if input[0] =~ /\-\-text=yes/i
    text_output = true
    input.shift
  elsif input[0] =~ /\-\-text=no/i
    text_output = false
    input.shift
  end

  # No quote source or symbols
  #
  if ! input[0]
    src_err << "#{$0}: Error: No quote source given. Quote source must be one of the following:<br>\n"
    ticker_src.each do |src_name, src_val|
      src_err << "  %s - %s<br>\n" % [ src_name, src_val ]
    end
    return_error( src_err + "\n" )
  elsif ! input[1]
    return_error( "#{$0}: Error: No symbols given." )
  end

  # Check for invalid quote source
  #
  src_found = false
  ticker_src.keys.each do |src_name|
    if src_name == input[0].downcase
      src_found = true
    end
  end

  # Throw an error unless we had a valid quote source
  #
  unless src_found
    src_err << "#{$0}: Error: Invalid quote source \"#{input[0]}\". "
    src_err << "Quote source must be one of the following:<br>\n"
    ticker_src.each do |src_name, src_val|
      src_err << "  %s - %s<br>\n" % [ src_name, src_val ]
    end
    return_error( src_err )
  end

  # So everything matched, send out args back
  #
  return input

end

# Return each ticker data 
#
def print_data( quotes, source, ticker )
  key = value = name = output = nil
  data = {}
  detail_data = {}
  ticker = ticker.upcase if source != 'tiaacref'

  # Our hash of stuff that we want to return as table rows
  # We have our default, and then add to it if $verbose is set
  #
  data['a_last_price'] = quotes[ticker][:last]
  data['b_change'] = quotes[ticker][:net]

  detail_data['a_Name'] = quotes[ticker][:name]
  detail_data['b_Todays_Range'] = quotes[ticker][:day_range]

  lt_date = quotes[ticker][:date]

  mm, dd, yy = lt_date.split( /\// )
  new_date = MONTHS[mm.to_i-1] + ' ' + dd + ', ' + yy

  detail_data['c_Last_Trade'] = new_date + " at " + quotes[ticker][:time]
  detail_data['d_52_Week_Range'] = quotes[ticker][:year_range]
  detail_data['e_PE_Ratio'] = quotes[ticker][:pe]
  detail_data['f_Earnings_per_share'] = quotes[ticker][:eps]
  detail_data['g_Volume'] = quotes[ticker][:volume] + ' shares'
  detail_data['h_Exchange'] = quotes[ticker][:exchange]
  #p quotes[ticker][:exchange]

  # Volume needs commas to look good
  #
  detail_data['g_Volume'].reverse!
  detail_data['g_Volume'].gsub!( /(\d\d\d)(?=\d)(?!\d*\.)/, "\\1," )
  detail_data['g_Volume'].reverse!

  output = ticker + ' '

  sorted_data = data.keys.sort

  sorted_data.each do |key|
    name = key.dup              # Need to save $key for hash lookups
    name.sub!( /^[a-z]_/, '' )  # Get rid of sorting characters
    name.tr!( '_', ' ' )        # Get rid of underscores
    data[key] = 'N/A' unless data[key]     # Don't show empty values

    # We want at least two decimal places in some fields
    #
    if name =~ /Change|Last|High|Low|Open|Close|Bid|Ask/i
      data[key].sub!( /^(\d+$)$/, "\\1.00" )
      data[key].sub!( /^(\d+\.\d)$/, "\\1\0/" )
    end

    if TEXT_OUTPUT
      output << "%s: %s \n " % [ name, data[key] ]
    else
      output << data[key] + ' '
    end
  end

  output << DELIM

  sorted_detail_data = detail_data.keys.sort

  sorted_detail_data.each do |key|
    name = key.dup              # Need to save $key for hash lookups
    name.sub!( /^[a-z]_/, '' )  # Get rid of sorting characters
    name.tr!( '_', ' ' )        # Get rid of underscores
    detail_data[key] = 'N/A' unless detail_data[key] # Don't show empty values

    # We want at least two decimal places in some fields
    #
    if name =~ /Change|Last|High|Low|Open|Close|Bid|Ask/i
      detail_data[key].sub!( /^(\d+$)$/, "\\1.00" ) if detail_data[key]
      detail_data[key].sub!( /^(\d+\.\d)$/, "\\1\0" ) if detail_data[key]
    end

    output << "%s: %s%s" % [ name, detail_data[key], DELIM ]
  end

  output << "\n"

  return output
end


# Prints a usage and error message to STDOUT, exits with -1
#
def return_error( error )
  usage = "Usage: #{$0} [--verbose=yes|no] &lt;quote_source&gt; &lt;ticker1&gt; [ticker2] ...\n";
  puts error + "<br>" + usage
  exit
end # End ReturnError

# Our defaults, can be modified from args; see readme
#
FONT_SIZE = 2
VERBOSE = false
TEXT_OUTPUT = false
DELIM = "!"

MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]

stock_dir = ".smStockReports2"

#
#  Change to users home directory. We used to dump into /tmp
#  but using home dir instead avoids multiple users interfering
#  with one another. (Yeah, we could "unique-ize" the filenames, but
#  this is easier for now...)
#
home = ENV['HOME'] || Etc.getpwuid( Process.uid ).name
Dir.chdir || Dir.chdir( home )

Dir.mkdir( stock_dir, 0755 ) unless File.directory?( stock_dir )
Dir.chdir( stock_dir )

data_file_name = 'stockinfo.dat'

#
#  Write out the stuff we need to the Data File. This is the file that will
#  be read by GKrellStock.
#
fh = File.new( data_file_name, 'w')

tickers = ARGV

# Get rid of puncuation in @ARGV.  This will filter out metacharacters and
# such, which makes the script marginally more safe
#
tickers.each { |ticker| ticker.gsub!( /[^a-zA-Z0-9\-=_\.\^]/, '' ) }

# Check the input to make sure we can look stuff up
#
tickers = check_input( tickers )

# Get a new Finance::Quote object
#
quote_src = tickers[0]

quote = nil
if quote_src == 'asia'
  quote = Finance::Quote.new( Yahoo::Asia )
else
  quote = Finance::Quote.new
end

# Override LWP's 120 second timeout, throw error if we time out
# Set this pretty short if using as a server-side exec
#
# ReturnError("No data available. $!\n") unless ($quote->timeout(10));

# Load the quotes hash for getting data
#
#my %quotes = $quote->$quote_src(@tickers);

tickers.shift		# Get rid of the quote source
quotes = quote.fetch( quote_src, tickers )

# get the ticker data, print it out
#
#shift(@tickers);		# Get rid of the quote source
if TEXT_OUTPUT
  tickers.each { |ticker| puts print_data( quotes, quote_src, ticker ) }
else
  tickers.each do |ticker|
    text_line = print_data( quotes, quote_src, ticker )
    fh.puts text_line
  end
end

fh.close
