Ruby: File.open how to pass file as arguments - ruby

Sorry, this may be a bit of a noob question. This (economic.rb) is a script that parses some world economic data. I'm unsure how to pass the xml file to it. Normally, to run this I would do
ruby economic.rb
However, File.open is taking the ARGV[0] as a parameter. How do I pass the xml file (data.xml) into that when running the script.
economic.rb
require 'rubygems'
require 'nokogiri'
File.open(ARGV[0]) do |f|
xml_doc = Nokogiri::XML::Document.parse(f)
countries = xml_doc.css('country')
most_populous = countries.max_by {|node| node['population'].to_i}
puts "The most populous country in 1996 was #{most_populous['name']} with a population of #{most_populous['population']}"
puts
puts "The five countries with the highest inflation rate in 1996 were:"
countries.sort_by {|country| -(country['inflation'] || 0).to_f} [0..4].each do |country|
puts " #{country['name']} - #{country['inflation']}%"
end
continent_info = countries.group_by {|country| country['continent']}
puts
puts "The continents and their countries in 1996 were:"
continent_info.keys.sort.each do |continent|
continent_info[continent].sort_by {|country|
country['name']}.each do |country|
puts " #{country['name']}"
end
end

You can just run:
ruby economic.rb data.xml

Related

loop through json array and retrieve one attribute, gives errors also

i am new to programming in ruby, and i am trying to get the value of json['earning_rate_hr'] but i get an error, in '[]': no implicit conversion of String into Integer (TypeError)
i know and i understand the error, however this is not my main question here is my file :
checkingchecker.rb :
#require_relative '../lib/hackex/net/typhoeus'
require_relative '../lib/hackex'
require 'rubygems'
require 'json'
file = 'accounts1.txt'
f = File.open file, 'r'
puts "MADE BY THE PEOPLE, FOR THE PEOPLE #madebylorax"
puts ""
puts "--------------------------------------------------------"
puts ""
while line = f.gets
line = line.chomp.split(';')
email, password = line
puts "logging in as " + email
HackEx.LoginDo(email, password) do |http, auth_token, user|
puts "getting info..."
user = HackEx::Request.Do(http, HackEx::Request.UserInfo(auth_token))['user']
puts "receieved user info!"
bank = HackEx::Request.Do(http, HackEx::Request.UserBank(auth_token))['user_bank']
puts "recieved bank info!"
json = HackEx::Request.Do(http, HackEx::Request.UserSpam(auth_token))['spam']
puts "recieved spam info!"
puts json['earning_rate_hr'] #error line, the error is because this is an array, and it cant be turned into integer, i was wondering if there is a way to use puts on it without trying to make it an integer
userchecking = bank["checking"]
checking = userchecking.scan(/.{1,3}/).join(',')
puts email + " has in Checking: BTC #{checking}"
puts ""
puts "--------------------------------------------------------"
puts ""
end
end
i tried to do puts json, it puts items like this one :
{"id"=>"9867351", "user_id"=>"289108", "victim_user_id"=>"1512021",
"victim_ip"=
"86.60.226.175", "spam_level"=>"50", "earning_rate_hr"=>"24300", "total_earning s"=>"13267800", "started_at"=>"2015-11-01 07:46:59",
"last_collected_at"=>"2015- 11-24 01:46:59"}
what i want to do is select the earning_rate_hr for each one of them and add them together, however i do not have a clue on how to do that, since the error is not fixed and i cant get the value of it
ps : i tried turning it into a Hash, and i also tried using .first, but .first only shows the firs one, i want to show all of them, thank you
I know you from line messenger, I haven't used ruby codes in a long time and this one keeps giving me cloudflare errors, I'm not sure if its because of server downtime/maintainance or whatever but yeah anyway heres your script, enjoy farming ;) -LineOne
PS, I changed a few strings to make it look a lil cleaner so you can see the spam income easier, and added the sleep (1) because sleeping for one second before reconnecting helps to prevent cloudflare errors
also you don't need to require json or rubygems in your hackex scripts because its required in the library so its all covered pre-user-input/script
require_relative 'libv5/lib/hackex'
while 1<2
begin
print'Filename: '
fn=gets.chomp
file = fn+'.txt'
f = File.open file, 'r'
puts "MADE BY THE PEOPLE, FOR THE PEOPLE #madebylorax" #helped by lineone
puts ""
puts "--------------------------------------------------------"
puts ""
while line = f.gets
line = line.chomp.split(';')
email, password = line
HackEx.LoginDo(email, password) do |http, auth_token, user|
puts "Retrieving Info..."
puts''
user = HackEx::Request.Do(http, HackEx::Request.UserInfo(auth_token))['user']
bank = HackEx::Request.Do(http, HackEx::Request.UserBank(auth_token))['user_bank']
json = HackEx::Request.Do(http, HackEx::Request.UserSpam(auth_token))['spam']
cash_count=0
tot_count=0
json.each do |j|
earn_rate = j['earning_rate_hr']
total= j['total_earnings']
cash_count+=earn_rate.to_i
tot_count+=total.to_i
end
print "#{email}: current earnings: #{cash_count} per hour, Total earnings #{tot_count},"
userchecking = bank["checking"]
checking = userchecking.scan(/.{1,3}/).join(',')
puts " #{checking} BTC in Checking"
puts ""
puts "--------------------------------------------------------"
puts ""
sleep 1
end
end
rescue
puts"#{$!}"
end
end
Thats fine you can also calculate the total income of your farms by adding new variables at the top example a=0 then adding the number at the end a+=tot_count
This should help:
earning_rates = json.map{|e| e["earning_rate_hr"]}
puts "Earning rates per hour: #{earning_rates.join(" ")}"
puts "Sum of earning rates: #{earning_rates.map{|e| e.to_i}.inject{|sum, x| sum + x}}"

Getting page title with Ruby

I am trying to get what's inside of the title tag but I can't get to do it. I am following some of the answers around stackoverflow that are supposed to work but for me they don't.
This is what I am doing:
require "open-uri"
require "uri"
def browse startpage, depth, block
if depth > 0
begin
open(startpage){ |f|
block.call startpage, f
}
rescue
return
end
end
end
browse("https://www.ruby-lang.org/es/", 2, lambda { |page_name, web|
puts "Header information:"
puts "Title: #{web.to_s.scan(/<title>(.*?)<\/title>/)}"
puts "Base URI: #{web.base_uri}"
puts "Content Type: #{web.content_type}"
puts "Charset: #{web.charset}"
puts "-----------------------------"
})
The title output is just [], why?
open returns a File object or passes it to the block (actually a Tempfile but that doesn't matter). Calling to_s just returns a string containing the object's class and its id:
open('https://www.ruby-lang.org/es/') do |f|
f.to_s
end
#=> "#<File:0x007ff8e23bfb68>"
Scanning that string for a title is obviously useless:
"#<File:0x007ff8e23bfb68>".scan(/<title>(.*?)<\/title>/)
Instead, you have to read the file's content:
open('https://www.ruby-lang.org/es/') do |f|
f.read
end
#=> "<!DOCTYPE html>\n<html>\n...</html>\n"
You can now scan the content for a <title> tag:
open('https://www.ruby-lang.org/es/') do |f|
str = f.read
str.scan(/<title>(.*?)<\/title>/)
end
#=> [["Lenguaje de Programaci\xC3\xB3n Ruby"]]
or, using Nokogiri: (because You can't parse [X]HTML with regex)
open('https://www.ruby-lang.org/es/') do |f|
doc = Nokogiri::HTML(f)
doc.at_css('title').text
end
#=> "Lenguaje de Programación Ruby"
If you must insist on using open-uri, this one liner than get you the page title:
2.1.4 :008 > puts open('https://www.ruby-lang.org/es/').read.scan(/<title>(.*?)<\/title>/)
Lenguaje de Programación Ruby
=> nil
If you want to use something more complicated than this, please use nokogiri or mechanize. Thanks

How do I parse XML nodes from an API request?

How do I save the information from an XML page that I got from a API?
The URL is "http://api.url.com?number=8-6785503" and it returns:
<OperatorDataContract xmlns="http://psgi.pts.se/PTS_Number_Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>Tele2 Sverige AB</Name>
<Number>8-6785503</Number>
</OperatorDataContract>
How do I parse the Name and Number nodes to a file?
Here is my code:
require 'rubygems'
require 'nokogiri'
require 'open-uri'
url = "http://api.url.com?number=8-6785503"
doc = Nokogiri::XML(open(url))
File.open("exporterad.txt", "w") do |file|
doc.xpath("//*").each do |item|
title = item.xpath('//result[group_name="Name"]')
phone = item.xpath("/Number").text.strip
puts "#{title} ; \n"
puts "#{phone} ; \n"
company = " #{title}; #{phone}; \n\n"
file.write(company.gsub(/^\s+/,''))
end
end
Besides the fact that your code isn't valid Ruby, you're making it a lot harder than necessary, at least for a simple scrape and save:
require 'nokogiri'
require 'open-uri'
url = "http://api.pts.se/PTSNumberService/Pts_Number_Service.svc/pox/SearchByNumber?number=8-6785503"
doc = Nokogiri::XML(open(url))
File.open("exported.txt", "w") do |file|
name = doc.at('Name').text
number = doc.at('Number').text
file.puts name
file.puts number
end
Running that results in a file called "exported.txt" that contains:
Tele2 Sverige AB
8-6785503
You can build upon that as necessary.

Trying to figure out why Ruby is throwing error on some basic classes & methods

I have written some code out of the Ruby Pickaxe book and I am trying to get it to work.
(around page 62 of "Programming Ruby The Pragmatic Programmer's Guide")
**Edit: More info on the book: (C) 2009, for Ruby 1.9
Given this error message, I am not quite sure how to identify what is going wrong. I appreciate any help in understanding what is going wrong here.
How does one know what to identify and solve?
I am wondering if Ruby's CSV functionality is really just this easy-- no gem/bundle install to run?
I would really like to be able to run my test_code.rb file, but I am unable to figure out this error.
Thank you for your time,
Patrick
Note: all of these files are in the same directory.
IRB command, followed by the error message it generates:
2.1.1 :005 > load "test_code.rb"
LoadError: cannot load such file -- csv-reader
from /Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from /Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from test_code.rb:3:in `<top (required)>'
from (irb):5:in `load'
from (irb):5
from /Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'
I don't know how relevant this is, based on the error message, but thought I'd include it.
kernel_require.rb line 55:
if Gem::Specification.unresolved_deps.empty? then
begin
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.enter
end
end
line 9-11 of irb:
require "irb"
IRB.start(__FILE__)
First file of program: csv-reader.rb
require 'csv'
require 'book-in-stock'
class CsvReader
def initialize
#books_in_stock = []
end
def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
#books_in_stock << BookInStock.new(row["ISBN"], row["Amount"])
end
end
def total_value_in_stock
sum = 0.0
#books_in_stock.each {|book| sum += book.price}
end
def number_of_each_isbn
end
end
Second file: book-in-stock.rb
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
def price_in_cents
Integer(price*100 + 0.5)
end
def price_in_cents=(cents)
#price = cents / 100.0
end
end
Third file: stock-stats.rb
require 'csv-reader'
reader = CsvReader.new
ARGV.each do |csv_file_name|
STDERR.puts "Processing #{csv_file_name}"
reader.read_in_csv_data(csv_file_name)
end
puts "Total value = #{reader.total_value_in_stock}"
Fourth file: test_code.rb
# this is the test code file
require 'csv-reader'
require 'book-in-stock'
require 'stock-stats'
# code to call
reader = CsvReader.new
reader.read_in_csv_data("file1.csv")
reader.read_in_csv_data("file2.csv")
puts "Total value in stock = #{reader.total_value_in_stock}"
# code to call
book = BookInStock.new("isbn1", 33.80)
puts "Price = #{book.price}"
puts "Price in cents = #{book.price_in_cents}"
book.price_in_cents = 1234
puts "Price = #{book.price}"
puts "Price in cents = #{book.price_in_cents}"
CSV files:
file1.csv
ISBN, Amount
isbn1, 49.00
isbn2, 24.54
isbn3, 33.23
isbn4, 15.55
file2.csv
ISBN, Amount
isbn5-file2, 39.98
isbn6-file2, 14.84
isbn7-file2, 43.63
isbn8-file2, 25.55
Edit
After Frederick Cheung's suggestion to change require to require_relative (for all but the 1st line of csv-reader.rb), the script is running, but a method is not working (see below)
(I did receive an error about this line:
#price = Float(price)
and changed it to #price = price.to_f and it runs just fine. )
3 Questions:
-> I changed the header of my csv files to "ISBN, Amount". Previously Amount was amount (not capitalized). Does this matter (i.e. the capitalizing of the header)?
-> While we're on the subject, what is the "row" keyword doing in the following #read_in_csv_data method?
-> Now that my code runs it appears that the output for "Total value in stock" is not summing up all of the prices in the csv file. Could a Rubyist please help me understand why this is happening?
The method
def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
#books_in_stock << BookInStock.new(row["ISBN"], row["Amount"])
end
end
and call seem fine to me...
reader = CsvReader.new
reader.read_in_csv_data("file1.csv")
reader.read_in_csv_data("file2.csv")
Here is the current output from terminal:
Total value = []
Price = 33.8
Price in cents = 3380
Price = 12.34
Price in cents = 1234
Total value in stock = [#<BookInStock:0xb8168a60 #isbn="isbn1", #price=0.0>, #<BookInStock:0xb8168740 #isbn="isbn2", #price=0.0>, #<BookInStock:0xb8168358 #isbn="isbn3", #price=0.0>, #<BookInStock:0xb81546f0 #isbn="isbn4", #price=0.0>, #<BookInStock:0xb8156a18 #isbn="isbn5-file2", #price=0.0>, #<BookInStock:0xb8156784 #isbn="isbn6-file2", #price=0.0>, #<BookInStock:0xb81564a0 #isbn="isbn7-file2", #price=0.0>, #<BookInStock:0xb8156248 #isbn="isbn8-file2", #price=0.0>]
Thanks again.
Edit: Big thanks to 7Stud for a very thorough followup answer on every question I had. You have been exceptionally helpful. I have learned several important things thanks to your post.
Edit:
Still not able to get the code to run.
I am not sure how to add to / edit the $LOAD_PATH, so I tried putting all of the files into this directory:
directory: ~MY_RUBY_HOME/lib/ruby/site_ruby/2.1.0/csv-reader
(i.e. /Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/csv-reader)
However, I still receive the same error message:
✘  ~MY_RUBY_HOME/lib/ruby/site_ruby/2.1.0/csv-reader  ruby test_code.rb file1.csv file2.csv
/Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- ./csv_reader (LoadError)
from /Users/patrickmeaney/.rvm/rubies/ruby-2.1.1/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from test_code.rb:1:in `<main>'
I have written some code out of the Ruby Pickaxe book
Yeah, but there are many Ruby Pickaxe books.
IRB command, followed by the error message it generates:
NEVER run anything in IRB. Never use IRB for ANYTHING. Instead put your code in a file, and then run the file, e.g:
$ ruby my_prog.rb
LoadError: cannot load such file -- csv-reader
If the files you want to require are not located in the directories ruby searches automatically(to see those directories execute the line `p $LOAD_PATH'), then you can specify the absolute or relative path to the file you want to require in the require statement:
require './book_in_stock'
I did receive an error about this line: #price = Float(price) and
changed it to #price = price.to_f and it runs just fine.
x = 'hello'
p x.to_f
p Float(x)
--output:--
0.0
1.rb:3:in `Float': invalid value for Float(): "hello" (ArgumentError)
from 1.rb:3:in `<main>
The difference between Float() and to_f() is that Float will raise an exception when it is unable to convert the String to a Float, while to_f() will return 0 when it cannot convert the String to a Float. Unless you know what you are doing, it's probably best to use Float(), so that you are alerted to the fact that your data has an error in it.
While we're on the subject, what is the "row" keyword doing in the
following #read_in_csv_data method?
When you loop through the rows of your file(e.g. CSV.foreach), csv converts one row of your file into a thing called a "CSV::Row", and then assigns the "CSV::ROW" object to the loop variable, which you have named "row":
CSV.foreach(csv_file_name, headers: true) do |row|
^
|
So "row" is a variable that refers to a "CSV::Row". A "CSV::Row" acts like a hash, enabling you to write things like row['ISBN'] to retrieve the value in that column.
Spaces are significant in csv files. If your header row is ISBN, Amount, then the column names are "ISBN" and " Amount" (see the leading space?). That means there is no value for
row['Amount']
i.e. it will return nil, but there is a value for
row[' Amount']
^
|
Now that my code runs it appears that the output for "Total value in
stock" is not summing up all of the prices in the csv file. Could a
Rubyist please help me understand why this is happening?
1) A def returns the value of the last statement that was executed in the def.
2) Array#each() returns the array.
Here is your def:
def total_value_in_stock
sum = 0.0
#books_in_stock.each {|book| sum += book.price}
end
That def returns the #books_in_stock array. You need to return the sum:
def total_value_in_stock
sum = 0.0
#books_in_stock.each {|book| sum += book.price}
sum
end
If you want to get tricky, you can have csv automatically convert any data in your file that looks like a number to a number:
CSV.foreach(
csv_file_name,
headers: true,
:converters => :numeric
) do |row| ...
...then your BookInStock class would look like this:
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn, price)
#isbn = isbn
#price = price #Float(price)
end
Here are all your files amended so they will run correctly:
csv_reader.rb:
require 'csv'
require './book_in_stock'
class CsvReader
def initialize
#books_in_stock = []
end
def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
#books_in_stock << BookInStock.new(row["ISBN"], row["Amount"])
end
end
def total_value_in_stock
sum = 0.0
#books_in_stock.each {|book| sum += book.price}
sum
end
def number_of_each_isbn
end
end
stock_stats.rb:
require './csv_reader'
reader = CsvReader.new
ARGV.each do |csv_file_name|
STDERR.puts "Processing #{csv_file_name}"
reader.read_in_csv_data(csv_file_name)
end
puts "Total value = #{reader.total_value_in_stock}"
test_code.rb:
require './csv_reader'
require './book_in_stock'
require './stock_stats'
reader = CsvReader.new
reader.read_in_csv_data("file1.csv")
reader.read_in_csv_data("file2.csv")
puts "Total value in stock = #{reader.total_value_in_stock}"
# code to call
book = BookInStock.new("isbn1", 33.80)
puts "Price = #{book.price}"
puts "Price in cents = #{book.price_in_cents}"
book.price_in_cents = 1234
puts "Price = #{book.price}"
puts "Price in cents = #{book.price_in_cents}"
book_in_stock.rb:
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
def price_in_cents
Integer(price*100 + 0.5)
end
def price_in_cents=(cents)
#price = cents / 100.0
end
end
file1.csv:
ISBN,Amount
isbn1,49.00
isbn2,24.54
isbn3,33.23
isbn4,15.55
file2.csv:
ISBN,Amount
isbn5-file2,39.98
isbn6-file2,14.84
isbn7-file2,43.63
isbn8-file2,25.55
Now run the program:
~/ruby_programs$ ruby test_code.rb file1.csv file2.csv
Processing file1.csv
Processing file2.csv
Total value = 246.32
Total value in stock = 246.32
Price = 33.8
Price in cents = 3380
Price = 12.34
Price in cents = 1234
require searches for files in Ruby's load path (this is stored in the global variables $: or $LOAD_PATH)
The current directory is not in the load path by default (it used to be in ruby 1.8 and earlier) which is why ruby says that it can't find csv-reader
You can add to the load path either by manipulating the $: variable (it behaves just like an array) or with the the -I option.
For example if you launch irb by doing
irb -I.
Then your code should run without modification (assuming there are no other problems with it)
Lastly you could switch your require statements to use require_relative - this locates files relative to the current file

how do I save the parsed data to a file

I wounder how I can save the parsed data to a txt file. My script is only saving the last parsed. Do i need to add .each do ? kind of lost right now
here is my code and if maybe somebody could explain to me how save the parsed info on a new line
here is the code
require 'rubygems'
require 'nokogiri'
require 'open-uri'
url = "http://www.clearsearch.se/foretag/-/q_advokat/1/"
doc = Nokogiri::HTML(open(url))
doc.css(".gray-border-bottom").each do |item|
title = item.css(".medium").text.strip
phone = item.css(".grayborderwrapper > .bold").text.strip
adress = item.css(".grayborder span").text.strip
www = item.css(".click2www").map { |link| link['href'] }
puts "#{title} ; \n"
puts "#{phone} ; \n"
puts "#{adress} ; \n"
puts "#{www} ; \n\n\n"
puts "Writing"
company = "#{title}; #{phone}; #{adress}; #{www} \n\n"
puts "saving"
file = File.open("exporterad.txt", "w")
file.write(company)
file.close
puts "done"
end
puts "done"
Calling File.open inside your loop truncates the file to zero length with each invocation. Instead, open the file outside your loop (using the block form):
File.open("exporterad.txt", "w") do |file|
doc.css(".gray-border-bottom").each do |item|
# ...
file.write(company)
# ...
end
end # <- file is closed automatically at the end of the block

Resources