I'm trying to create a document with Prawn Ruby PDF generator, but I'm facing the following problem:
The image below shows what structure I'm trying to do.
And this is the example code that tries to mimics my real scenario with the way that I'm trying to achieve this. The 2.times and (50.times.map { |i| i.to_s }.join("\n")) mimics dynamic data.
require 'prawn'
class MyPdf
def self.to_pdf(*args)
new(*args).to_pdf
end
def to_pdf
pdf.move_down 200
2.times do
pdf.bounding_box(
[0, pdf.cursor],
width: pdf.bounds.width
) do
pdf.text (50.times.map { |i| i.to_s }.join("\n"))
pdf.stroke_bounds
end
end
pdf
end
def pdf
#pdf ||= Prawn::Document.new(page_size: 'A4')
end
end
But I'm having a lot of trouble with the dynamic bounding box placing.
Do you people know a way to achieve this with or without bounding boxes?
You may be looking for span:
def to_pdf
pdf.move_down 200
2.times do
pdf.span(pdf.bounds.width) do
pdf.text (50.times.map { |i| i.to_s }.join("\n"))
pdf.stroke_bounds
end
end
end
I failed to do that with boundig boxes. May be this is not the best solution, but you can do this with tables :
data = []
500.times do |i|
data.push [i.to_s]
end
table(data, width: bounds.width) do |t|
t.cells.border_width = 0 # We don't want to see a table
t.before_rendering_page do |page|
page.row(0).border_top_width = 1
page.row(-1).border_bottom_width = 1
page.column(0).border_left_width = 1
page.column(-1).border_right_width = 1
end
end
source : http://prawnpdf.org/prawn-table-manual.pdf (page 17)
Check your margins to have the continuation on the top of the next page
Related
I have run across a strange bug I'm not sure how to solve. I am trying to make a program that solves Wordle in the browser using Ruby and Selenium. I noticed that starting with the second guess, the program would just keep guessing the same word instead of incorporating feedback and fetching a new word. So I added a byebug statement to see what was going on, and then it started working. If I remove the byebug statement, the bug appears again. I couldn't believe it so I pushed to Github and cloned on another computer and the same thing happened again. Basically, as long as I put a byebug statement anywhere after line 24, the code works as expected. Here is the code.
require 'webdrivers'
require './words.rb'
require 'byebug'
class WordleSolver
attr_reader :driver
def initialize
options = Selenium::WebDriver::Options.chrome
#driver = Selenium::WebDriver.for :chrome, options: options
#driver.manage.timeouts.implicit_wait = 10
#driver.get 'https://www.nytimes.com/games/wordle/index.html'
#game_div = driver.find_element(css: 'game-app').shadow_root.find_element(css: 'div#game')
#words = PossibleWords::WORDS
#word = ['', '', '', '', '']
#present = {}
#absent = []
#current_row = 1
end
def solve
close_modal
guess('store')
5.times do
get_feedback
break if game_won?
next_guess = formulate_guess
guess(next_guess)
sleep(3)
end
end
def game_won?
#word.each { |letter| return false if letter == '' }
true
end
def close_modal
#game_div.find_element(css: 'game-modal').shadow_root.find_element(css: 'div.close-icon').click
end
def guess(word)
#driver.action.send_keys(word).send_keys(:enter).perform
end
def formulate_guess
#words.each do |word|
return word if valid_guess?(word)
end
'guess'
end
def valid_guess?(word)
#word.each_with_index do |letter, index|
if letter != ''
return false if word[index] != letter
end
end
#absent.each do |letter|
return false if word.include?(letter)
end
#present.each do |letter, incorrect_positions|
return false if !word.include?(letter)
incorrect_positions.each do |position|
return false if word[position] == letter
end
end
end
def get_feedback
row = #game_div.find_element(css: "game-row:nth-of-type(#{#current_row})").shadow_root
1.upto(5) do |n|
tile = row.find_element(css: "game-tile:nth-of-type(#{n})")
letter = tile.attribute("letter")
evaluation = tile.attribute("evaluation")
add_feedback(letter, evaluation, n - 1)
end
#current_row += 1
end
def add_feedback(letter, evaluation, position)
case evaluation
when 'absent'
#absent.push(letter)
when 'present'
if #present[letter]
#present[letter].push(position)
else
#present[letter] = [position]
end
when 'correct'
#present.delete(letter)
#word[position] = letter if #word[position] == ''
end
end
end
begin
solver = WordleSolver.new
solver.solve
sleep(5)
ensure
solver.driver.quit
end
TLDR when I add a byebug statement to my code the bug I was investigating disappears
The comment by BroiSatse solved my issue. By adding a byebug statement I was also causing the program to wait, giving it time to synchronize with the browser, which fixed the bug.
I recently wrote a program to return a bunch of stocks from the stock market that are unhealthy. The basic algorithm is this:
Look up all the quotes of every stock in an exchange (either NYSE or NASDAQ)
Find the ones that are less than 5 dollars from step 1
Find the ones from step 2 that are down 3 days and have large volume (expensive because I have to make a request for each stock, which is like ~700 currently for nasdaq).
Scan the news for the ones returned by step 3.
I had this all in one file:
Original implementation (https://github.com/EdmundMai/minion/blob/aa14bc3234a4953e7273ec502276c6f0073b459d/lib/minion.rb):
require 'bundler/setup'
require "minion/version"
require "yahoo-finance"
require "business_time"
require 'nokogiri'
require 'open-uri'
module Minion
class << self
def query(exchange)
client = YahooFinance::Client.new
all_companies = CSV.read("#{exchange}.csv")
small_caps = []
ticker_symbols = all_companies.map { |row| row[0] }
ticker_symbols.each_slice(200) do |batch|
data = client.quotes(batch, [:symbol, :last_trade_price, :average_daily_volume])
small_caps << data.select { |stock| stock.last_trade_price.to_f < 5.0 }
end
attractive = []
small_caps.flatten!.each_with_index do |small_cap, index|
begin
data = client.historical_quotes(small_cap.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
closing_prices = data.map(&:close).map(&:to_f)
volumes = data.map(&:volume).map(&:to_i)
negative_3_days_in_a_row = closing_prices == closing_prices.sort
larger_than_average_volume = volumes.reduce(:+) / volumes.count > small_cap.average_daily_volume.to_i
if negative_3_days_in_a_row && larger_than_average_volume
attractive << small_cap.symbol
puts "Qualified: #{small_cap.symbol}, finished with #{index} out of #{small_caps.count}"
else
puts "Not qualified: #{small_cap.symbol}, finished with #{index} out of #{small_caps.count}"
end
rescue => e
puts e.inspect
end
end
final_results = []
attractive.each do |symbol|
rss_feed = Nokogiri::HTML(open("http://feeds.finance.yahoo.com/rss/2.0/headline?s=#{symbol}®ion=US&lang=en-US"))
html_body = rss_feed.css('body')[0].text
diluting = false
['warrant', 'cashless exercise'].each do |keyword|
diluting = true if html_body.match(/#{keyword}/i)
end
final_results << symbol if diluting
end
final_results
end
end
end
This was really fast and would finish processing like ~700 stocks in a minute or less.
Then, I tried refactoring and splitting up the algorithm into different classes and files without changing the algorithm at all. I decided on using the decorator pattern since it seems to fit. However when I run the program now, it makes each request really slowly (15+ min). I know this because my puts statements get printed out really slowly.
New and slower implementation (https://github.com/EdmundMai/minion/blob/master/lib/minion.rb)
require 'bundler/setup'
require "minion/version"
require "yahoo-finance"
require "minion/dilution_finder"
require "minion/negative_finder"
require "minion/small_cap_finder"
require "minion/market_fetcher"
module Minion
class << self
def query(exchange)
all_companies = CSV.read("#{exchange}.csv")
all_tickers = all_companies.map { |row| row[0] }
short_finder = DilutionFinder.new(NegativeFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers))))
short_finder.results
end
end
end
The part it's lagging at according to my puts:
require "yahoo-finance"
require "business_time"
require_relative "stock_finder"
class NegativeFinder < StockFinder
def results
client = YahooFinance::Client.new
results = []
finder.results.each_with_index do |stock, index|
begin
data = client.historical_quotes(stock.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
closing_prices = data.map(&:close).map(&:to_f)
volumes = data.map(&:volume).map(&:to_i)
negative_3_days_in_a_row = closing_prices == closing_prices.sort
larger_than_average_volume = volumes.reduce(:+) / volumes.count > stock.average_daily_volume.to_i
if negative_3_days_in_a_row && larger_than_average_volume
results << stock
puts "Qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}"
else
puts "Not qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}"
end
rescue => e
puts e.inspect
end
end
results
end
end
It's lagging on step 3 (making one request for each stock). Not sure what's going on so any advice would be appreciated. If you want to clone the program and run it, just comment in the last line in lib/minion.rb and type ruby lib/minion.rb
After debugging it I figured it out. It was because I was calling finder.results (results being the decorated method) inside of the loop as shown below:
require 'bundler/setup'
require "minion/version"
require "yahoo-finance"
require "minion/dilution_finder"
require "minion/negative_finder"
require "minion/small_cap_finder"
require "minion/market_fetcher"
module Minion
class << self
def query(exchange)
all_companies = CSV.read("#{exchange}.csv")
all_tickers = all_companies.map { |row| row[0] }
short_finder = DilutionFinder.new(NegativeFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers))))
short_finder.results
end
end
end
The part it's lagging at according to my puts:
require "yahoo-finance"
require "business_time"
require_relative "stock_finder"
class NegativeFinder < StockFinder
def results
client = YahooFinance::Client.new
results = []
finder.results.each_with_index do |stock, index|
begin
data = client.historical_quotes(stock.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
closing_prices = data.map(&:close).map(&:to_f)
volumes = data.map(&:volume).map(&:to_i)
negative_3_days_in_a_row = closing_prices == closing_prices.sort
larger_than_average_volume = volumes.reduce(:+) / volumes.count > stock.average_daily_volume.to_i
if negative_3_days_in_a_row && larger_than_average_volume
results << stock
// HERE!!!!!!!!!!!!!!!!!!!!!!!!!
puts "Qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}" <------------------------------------
else
// AND HERE!!!!!!!!!!!!!!!!!!!!!!!!!
puts "Not qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}" <-----------------------------------------------------------
end
rescue => e
puts e.inspect
end
end
results
end
end
This caused a cascade of requests every time I iterated through the loop in NegativeFinder. Removing that call fixed it. Lesson: When using the decorator pattern, either only call the decorated method once, especially when you're doing something expensive in each call. Either that or hold the returned variable in an instance variable so you don't have to calculate it each time.
Also as a side note, I've decided not to go with the decorator pattern because I don't think it applies well here. Something like SmallCapFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers))) doesn't add functionality at all (the primary function of using the decorator pattern), so chaining decorators doesn't do anything. Therefore, I'm just going to make them methods instead of adding unnecessary complexity.
There are some thing missing in the code you gave us (Base class StockFinder, MarketFetcher). But I think you are now instantate more than one YahooFinance::Client. Input/Output to other systems is very often the cause for speed problems.
I suggest that you first encapsulate the finance client and access to financial data. This makes it easier when you want to switch your financial data provider, or add another one. Instead of the decorator pattern, I would just use plain old methods for finding small caps, finding negative, etc.
I am creating an image slideshow in ruby, using gtk pixbuf to load images. I am very new to ruby & GTK, this may be an stupid question.
Currently image changes are linked to the GUI button_press_event, I would like them to change /refresh automatically based on a set time, like a slideshow or animation. I saw the gtk animation using a gif method, but I would like to use individual jpeg files inline sequence, so that I can set the time to show a slide. Once the loop has gone through all the images, the GUI should display buttons for replay or quit. ( I haven't used #time yet, it is just there for possibilities ) Thanks for any suggestions;
require 'gtk2'
class Pics
attr_accessor :pile, :picindex, :imgLoaded, :image, :box, :window, :time
def initialize
#window = Gtk::Window.new()
#window.signal_connect("destroy"){Gtk.main_quit}
pic1 = "1.jpg"
pic2 = "2.jpg"
pic3 = "3.jpg"
pic4 = "4.jpg"
#pile = [pic1, pic2, pic3, pic4]
#picindex = 0
self.getImage
#box = Gtk::EventBox.new.add(#image)
#time = true
end
def nuImage
#box.remove(#image)
#picindex = #picindex + 1
#picindex = 0 if #picindex == #pile.length
self.getImage
#box.add(#image)
#box.show
end
def getImage
#imgLoaded = #pile[#picindex]
img = Gdk::Pixbuf.new(#imgLoaded, 556, 900)
#image = Gtk::Image.new(img)
#image.show
end
end # class Pics
pics = Pics.new
pics.box.signal_connect("button_press_event"){pics.nuImage}
pics.window.set_default_size(556, 900)
pics.window.add(pics.box)
pics.window.show_all
Gtk.main
use GLib.timeout_add () or GLib.timeout_add_seconds (). Return False if you don't want to use it anymore.read GLib documentation, Section: Main Event Loop
This is a solution:
def start_button__clicked(but)
#thread = Thread.new {
loop do
next_button__clicked
sleep(2)
end
end
def stop_button__clicked(but)
#thread.kill
end
This is how I would do it in visual ruby. Its basically the same.
You'd just have a form with a button named "start_button" and "stop_button" etc.
the following code is an implementation:
GLib::Timeout.add(1000) do
pics.nuImage if pics.time
true
end
pics.window.signal_connect("key_press_event") do |_window, event|
case event.keyval
when Gdk::Keyval::GDK_KEY_space
pics.time = !pics.time
end
end
more details:
http://ruby-gnome2.sourceforge.jp/hiki.cgi?GLib%3A%3ATimeout
I have written this piece of code, which outputs a list of jobdescriptions (in Danish). It works fine, however I would like to alter the output a bit. The function is recursive because the jobs are nested, however the output does not show the nesting.
How do I configure the function to show an output like this:
Job 1
- Job 1.1
- Job 1.2
-- Job 1.2.1
And so on...
require 'nokogiri'
require 'open-uri'
def crawl(url)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'])
end
end
end
crawl('/Job.aspx')
require 'nokogiri'
require 'open-uri'
def crawl(url, nesting_level = 0)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts " " * nesting_level + txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'], nesting_level + 1)
end
end
end
crawl('/Job.aspx')
I see two options:
Pass an additional argument to the recursive function to indicate the level you are currently in. Initialize the value to 0 and each time you call the function increment this value. Something like this:
def crawl(url, level)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'], level + 1)
end
end
end
Make use of the caller array that holds the callstack. Use the size of this array to indicate the level in the recursion you are located in.
def try
puts caller.inspect
end
try
I would personally stick to the fist version as it seems easier to read, but requires you to modify the interface.
I'm looking for an easy way to get width and height dimensions for image files in Ruby without having to use ImageMagick or ImageScience (running Snow Leapard).
As of June 2012, FastImage which "finds the size or type of an image given its uri by fetching as little as needed" is a good option. It works with local images and those on remote servers.
An IRB example from the readme:
require 'fastimage'
FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
=> [266, 56] # width, height
Standard array assignment in a script:
require 'fastimage'
size_array = FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
puts "Width: #{size_array[0]}"
puts "Height: #{size_array[1]}"
Or, using multiple assignment in a script:
require 'fastimage'
width, height = FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
puts "Width: #{width}"
puts "Height: #{height}"
You could try these (untested):
http://snippets.dzone.com/posts/show/805
PNG:
IO.read('image.png')[0x10..0x18].unpack('NN')
=> [713, 54]
GIF:
IO.read('image.gif')[6..10].unpack('SS')
=> [130, 50]
BMP:
d = IO.read('image.bmp')[14..28]
d[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS')
JPG:
class JPEG
attr_reader :width, :height, :bits
def initialize(file)
if file.kind_of? IO
examine(file)
else
File.open(file, 'rb') { |io| examine(io) }
end
end
private
def examine(io)
raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI
class << io
def readint; (readchar << 8) + readchar; end
def readframe; read(readint - 2); end
def readsof; [readint, readchar, readint, readint, readchar]; end
def next
c = readchar while c != 0xFF
c = readchar while c == 0xFF
c
end
end
while marker = io.next
case marker
when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
length, #bits, #height, #width, components = io.readsof
raise 'malformed JPEG' unless length == 8 + components * 3
when 0xD9, 0xDA: break # EOI, SOS
when 0xFE: #comment = io.readframe # COM
when 0xE1: io.readframe # APP1, contains EXIF tag
else io.readframe # ignore frame
end
end
end
end
There's also a new (July 2011) library that wasn't around at the time the question was originally asked: the Dimensions rubygem (which seems to be authored by the same Sam Stephenson responsible for the byte-manipulation techniques also suggested here.)
Below code sample from project's README
require 'dimensions'
Dimensions.dimensions("upload_bird.jpg") # => [300, 225]
Dimensions.width("upload_bird.jpg") # => 300
Dimensions.height("upload_bird.jpg") # => 225
There's a handy method in the paperclip gem:
>> Paperclip::Geometry.from_file("/path/to/image.jpg")
=> 180x180
This only works if identify is installed. If it isn't, if PHP is installed, you could do something like this:
system(%{php -r '$w = getimagesize("#{path}"); echo("${w[0]}x${w[1]}");'})
# eg returns "200x100" (width x height)
I have finally found a nice quick way to get dimensions of an image. You should use MiniMagick.
require 'mini_magick'
image = MiniMagick::Image.open('http://www.thetvdb.com/banners/fanart/original/81189-43.jpg')
assert_equal 1920, image[:width]
assert_equal 1080, image[:height]
libimage-size is a Ruby library for calculating image sizes for a wide variety of graphical formats. A gem is available, or you can download the source tarball and extract the image_size.rb file.
Here's a version of the JPEG class from ChristopheD's answer that works in both Ruby 1.8.7 and Ruby 1.9. This allows you to get the width and height of a JPEG (.jpg) image file by looking directly at the bits. (Alternatively, just use the Dimensions gem, as suggested in another answer.)
class JPEG
attr_reader :width, :height, :bits
def initialize(file)
if file.kind_of? IO
examine(file)
else
File.open(file, 'rb') { |io| examine(io) }
end
end
private
def examine(io)
if RUBY_VERSION >= "1.9"
class << io
def getc; super.bytes.first; end
def readchar; super.bytes.first; end
end
end
class << io
def readint; (readchar << 8) + readchar; end
def readframe; read(readint - 2); end
def readsof; [readint, readchar, readint, readint, readchar]; end
def next
c = readchar while c != 0xFF
c = readchar while c == 0xFF
c
end
end
raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI
while marker = io.next
case marker
when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
length, #bits, #height, #width, components = io.readsof
raise 'malformed JPEG' unless length == 8 + components * 3
# colons not allowed in 1.9, change to "then"
when 0xD9, 0xDA then break # EOI, SOS
when 0xFE then #comment = io.readframe # COM
when 0xE1 then io.readframe # APP1, contains EXIF tag
else io.readframe # ignore frame
end
end
end
end
For PNGs I got this modified version of ChristopeD's method to work.
File.binread(path, 64)[0x10..0x18].unpack('NN')