Regex in Ruby to capture message - ruby

How can I achieve something like this:
listen_for /say (REGEX_THAT_CAPTURES_EVERYTHING_AFTER_SAY)/i do |message|
puts "Message: #{message}"
end
With what do I have to replace (REGEX_THAT_CAPTURES_EVERYTHING_AFTER_SAY) so that everything after say is captured:
Input
say Hello there
Result
Message: Hello there

/say (.+)$/
maybe?

Related

Ruby: how to ignore words in 'when'

How do I make the bot reply even if user’s message doesn’t 100% match with words I wrote in ‘when’? I want to make the bot trigger if someone sent “hello there”, for example, instead of just hello.
I’ve heard about when include?('example') but I can’t make it work.
require 'telegram/bot'
token = 'x'
Telegram::Bot::Client.run(token) do |bot|
bot.listen do |message|
puts "##{message.from.username}: #{message.text}"
case message.text
when 'hello'
bot.api.send_message(chat_id: message.chat.id, text: "answer")
end
end
end
You can use a regexp to match your text:
case message.text
when /hello/ # matches if there is the 'hello' somewhere in the string
# ...
when /\Ahello/ # matches if the string starts with 'hello'
# ...
end
Or you can use include? in an if condition:
if message.text.include?('hello')
# ...
end

Nested blocks and ERB

For the life of me, I can't figure out why this doesn't work as expected.
Code:
require 'erb'
def say_hello(name)
"Nice to see you, #{ name }!"
end
def greetings
template = <<-TEMPLATE
Hello!
<%= yield %>
Goodbye!
TEMPLATE
ERB.new(template).result(binding)
end
people = ['Aaron', 'Bob', 'Tim', 'Juan']
t = greetings do
people.each do |p|
say_hello(p)
end
end
puts t
(a bit contrived, I know, but it'll serve the point.)
What I Expect:
Hello!
Nice to see you, Aaron!
Nice to see you, Bob!
Nice to see you, Tim!
Nice to see you, Juan!
Goodbye!
What I Get:
Hello!
['Aaron', 'Bob', 'Tim', 'Juan']
Goodbye!
Thoughts:
I'm guessing this is happening because the interior block (beginning with people.each) gets coerced into a string before the block executes. Perhaps ERB doesn't like how I'm trying to inject a new block of constructed text into its template.
What's going on here?
The return value of each is the array itself, not the return value of the block:
people.each do |p|
say_hello(p)
end
# => ['Aaron', 'Bob', 'Tim', 'Juan']
You should use map, which returns the array of return values from the block:
people.map do |p|
say_hello(p)
end
# => ["Nice to see you, Aaron!", "Nice to see you, Bob!", "Nice to see you, Tim!", "Nice to see you, Juan!"]
You will also need to concatenate the array to render it properly:
t = greetings do
people.map do |p|
say_hello(p)
end.join("\n")
end

How can I handle using multiple variables depending on which element of an iterator I am using? Aka, how can I DRY up this ruby code?

I have some variables that look like this:
top_script_path = "path/to/top"
bottom_script_path = "path/to/bottom"
script_names = ["top", "bottom"]
and I'd like to call each of the scripts
`#{top_script_path} "top"`
puts "top script successful"
`#{bottom_script_path} "bottom"`
puts "bottom script successful"
This solution, however, doesn't feel DRY enough to me. I'd like to be able to do something like
script_names.each do |name|
`#{#{name}_script_path} #{name}`
puts "#{name} script successful"
end
Obviously, it isn't possible to put a #{expression} inside of a #{expression} as above, but is there any other way to dry up this code with a loop?
Use hashes:
script_paths = {
:top => 'path/to/top',
:bottom => 'path/to/bottom',
}
script_names = script_paths.keys
script_names.each do |name|
# `...`
puts "#{script_paths[name]} #{name}"
end
Run:
$ ruby qq.rb
path/to/top top
path/to/bottom bottom
script_names.each do |name|
`#{eval("#{name}_script_path")} #{name}`
puts "#{name} script successful"
end
I would refactor the variables into one structure, something like:
scripts = {
'top' => 'path/to/top',
'bottom' => 'path/to/bottom'
}
scripts.each do |name, path|
`#{path} #{name}`
puts "#{name} script successful"
end
If you are writing some kind of build script, consider using Rake.

Separating console message from the code

I'm trying to make command line apps. The puts line makes the code looks messy. For example, I have help command that has several puts
def help()
puts "Welcome to my app"
puts "..."
puts "..."
puts "..."
puts "..."
end
If I combine the puts into one, the output will include the trailing space
def help()
puts "Welcome to my app
...
..."
end
# The output in the console will be like:
# Welcome to my app
# ...
# ...
What's the best way to separate the message from the code? I can only think of using variable to store the message, but I believe there is a better, tidier way like markdown or using txt.
For what you are asking, I think you are looking for the OptParser library in STDLIB.
It allows you to build command line options for doing things like usage and command line reporting for the user.
However, you can do this in your help method:
def help
<<-EOS.lines.each {|line| line.strip!}
Welcome to my app
...
...
EOS
end
puts help
puts "Thank you for using my app!"
This will display like this.
Welcome to my app
...
...
Thank you for using my app!
Update: I changed the EOF delimiter to EOS for End of String.
def help
puts \
"Welcome to my app"\
"..."\
"..."\
"..."\
"..."\
"..."
end
In your specific example, You can do within the help function
puts "Welcome to my app", "...\n"*3
If you have a lots of such static messages, you can try using a hash somewhere at the beginning
messages = {"welcome" => "Welcome to my app\n" + "...\n"*3,
"thanks" => "Thank you for the action"}
Then you can access them as
puts messages["welcome"]

RSpec mocking an :each block

I want to use RSpec mocks to provide canned input to a block.
Ruby:
class Parser
attr_accessor :extracted
def parse(fname)
File.open(fname).each do |line|
extracted = line if line =~ /^RCS file: (.*),v$/
end
end
end
RSpec:
describe Parser
before do
#parser = Parser.new
#lines = mock("lines")
#lines.stub!(:each)
File.stub!(:open).and_return(#lines)
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
# HELP ME HERE ...
# the :each should be fed with 'linetext'
#lines.should_receive(:each)
#parser.should_receive('extracted=')
#parser.parse("somefile.txt")
end
end
It's a way to test that the internals of the block work correctly by passing fixtured data into it. But I can't figure out how to do the actual feeding with RSpec mocking mechanism.
update: looks like the problem was not with the linetext, but with the:
#parser.should_receive('extracted=')
it's not the way it's called, replacing it in the ruby code with self.extracted= helps a bit, but feels wrong somehow.
To flesh out the how 'and_yield' works: I don't think 'and_return' is really what you want here. That will set the return value of the File.open block, not the lines yielded to its block. To change the example slightly, say you have this:
Ruby
def parse(fname)
lines = []
File.open(fname){ |line| lines << line*2 }
end
Rspec
describe Parser do
it 'should yield each line' do
File.stub(:open).and_yield('first').and_yield('second')
parse('nofile.txt').should eq(['firstfirst','secondsecond'])
end
end
Will pass. If you replaced that line with an 'and_return' like
File.stub(:open).and_return(['first','second'])
It will fail because the block is being bypassed:
expected: ["firstfirst", "secondsecond"]
got: ["first", "second"]
So bottom line is use 'and_yield' to mock the input to 'each' type blocks. Use 'and_return' to mock the output of those blocks.
I don't have a computer with Ruby & RSpec available to check this, but I suspect you need to add a call to and_yields call [1] on the end of the should_receive(:each). However, you might find it simpler not to use mocks in this case e.g. you could return a StringIO instance containing linetext from the File.open stub.
[1] http://rspec.rubyforge.org/rspec/1.1.11/classes/Spec/Mocks/BaseExpectation.src/M000104.html
I would go with the idea of stubbing the File.open call
lines = "RCS file: hello,v\n", "bla bla bla\n"
File.stub!(:open).and_return(lines)
This should be good enough to test the code inside the loop.
This should do the trick:
describe Parser
before do
#parser = Parser.new
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
File.should_receive(:open).with("somefile.txt").and_return(linetext)
#parser.parse("somefile.txt")
#parser.extracted.should == "hello"
end
end
There are some bugs in the Parser class (it won't pass the test), but that's how I'd write the test.

Resources