Run ruby script and assign to var Groovy/Jenkins - ruby

Probably something simple but don't seem to be getting the output i want, but say i have a ruby script
class SlackNames
def developers
{ "Richard Lewis" => "/#richlewis",
"Name 2" => "/#name2",
"Name 3" => "/#name3"
}
end
def slack_handle
puts developers["#{ARGV[0]}"] if developers.key?("#{ARGV[0]}")
end
end
SlackNames.new.slack_handle
I would run this on the command line
ruby slack_names.rb "Richard Lewis"
Which as you can see will just return a keys given value, when i run this in a Groovy script how can i save that value to a variable?
-- Groovy
-- DEV_NAME here equals "Richard Lewis"
SLACK_NAME = sh """#!/bin/bash -l
ruby ruby_scripts/slack_names.rb \"${DEV_NAME}\"
"""
echo "${SLACK_NAME}"
When i echo out SLACK_NAME it comes back as blank
What am i doing wrong here ?
Thanks

Well first of all, you are calling the method from still within the class. (May just be a typo in the post)
Second, why ".key?("#{ARGV[0]}")" thingy? In ruby, if the hash doesn't have a value for a given key, it will just return nil and nil has the boolean value of false.
class SlackNames
def developers
{ "Richard Lewis" => "/#richlewis",
"Name 2" => "/#name2",
"Name 3" => "/#name3"
}
end
def slack_handle
developers["#{ARGV[0]}"] if developers["#{ARGV[0]}"]
end
end
puts SlackNames.new.slack_handle
This works correctly for me at least.
ruby slack_names.rb "Richard Lewis"
=> /#richlewis
I hope it helps and that I understood your problem correctly.

Just to add to CookieNinjas answer once i added this in groovy everything works as expected
SLACK_NAME = sh (
script: """#!/bin/bash -l
ruby ruby_scripts/slack_names.rb \"${DEV_NAME}\"
""",
returnStdout: true
)

Related

Accessing Ruby hash value via symbol key returning true not value

I'm trying to make a basic command line tool using command line arguments (starting simple and gradually building up). I am using Ruby and its OptionParser class to do this. I have the following code:
require 'optparse'
options = {}
OptionParser.new do |option|
option.banner = "Usage: todo_list.rb <list title> <tasks>"
option.on("-t", "--title", "Title of task list") do |value|
options[:title] = value
end
option.on("-c", "--content", "Content of task list (tasks)") do |value|
options[:content] = value
end
option.on("-h", "--help", "Show this help message") do ||
puts option
end
end.parse!
p options
p ARGV
if options[:title]
puts "Created task list with title: #{ options[:title] }"
end
if options[:content]
puts "Added task: #{ options[:content] }"
end
For reference I have been running the clt as todo_list.rb -t Test -c content.
In the final 2 if statements I am simply trying to access the value of the key :content/:title in the options hash if they exist (if they have been passed from the command line), however the program only returns true ("Created task list with title true") / ("Added task: true"), instead of the value ("Test" or "content")
Using p ARGV outputs ['Test', 'Content'] so the arguments are being passed correctly, I think. Using p options returns {:title=>true, :content=>true}.
I have no idea why this is happening. If anyone has a clue, any and all advice is appreciated. Thank you.
You need to tell the option parser that your switches require arguments:
option.on("-tTITLE", "--title TITLE", "Title of task list") do |value|
options[:title] = value
end
option.on("-cCONTENT", "--content CONTENT", "Content of task list (tasks)") do |value|
options[:content] = value
end
Otherwise the options are considered to be simple boolean flags.
The documentation for optparse covers this in the #make_switch section:
https://ruby-doc.org/stdlib/libdoc/optparse/rdoc/OptionParser.html#method-i-make_switch
but that's not entirely obvious unless you already know what you're looking for. You usually end up figuring out how it works by looking at the examples and experimenting, then you stumbling across the #make_switch method.

Parse and substitute markup in Ruby

I want to parse a simple string like:
"My name is **NAME**" to "My name is <strong>NAME</strong\>"
Note:
Cannot use any external gems, even though markdown gem might have done the job.
If I understood you correctly it should be quite simple:
text = "My name is **NAME**"
=> "My name is **NAME**"
text = text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
=> "My name is <strong>NAME</strong>"
I've tested it in irb with this command text.gsub(([a-zA-Z\s]*)(\*\*)([a-zA-Z\s]*)(\*\*)/,"\\1<strong>\\3</strong>")
UPDATED
Consider this, if you wish to handle some more cases:
class SurroundMarkup
def initialize(markup_map: {})
#markup_map = markup_map
end
def format(text)
text.tap do |formatted_text|
#markup_map.each do |markup, tag|
formatted_text.gsub!(/#{markup}(?<text>.*?)#{markup}/) do |m|
start_tag(tag) + $~[:text] + stop_tag(tag)
end
end
end
end
private
def start_tag(tag)
"<#{tag}>"
end
def stop_tag(tag)
"</#{tag}>"
end
end
And you can use as follows:
markup_map = {
/\*\*/ => "strong",
/\*/ => "i",
}
s = SurroundMarkup.new(markup_map: markup_map)
s.format("My name is **NAME**") #=> "My name is <strong>NAME</strong>"
s.format("*Ruby* is cool") #=> "<i>Ruby</i> is cool"

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.

How to test stdin for a CLI using rspec

I'm making a small Ruby program and can't figure out how to write RSpec specs that simulate multiple user command line inputs (the functionality itself works). I think this StackOverflow answer probably covers ground that is closest to where I am, but it's not quite what I need. I am using Thor for the command line interface, but I don't think this is an issue with anything in Thor.
The program can read in commands either from a file or the command line, and I've been able to successfully write tests to read in an execute them. Here's some code:
cli.rb
class CLI < Thor
# ...
method_option :filename, aliases: ['-f'],
desc: "name of the file containing instructions",
banner: 'FILE'
desc "execute commands", "takes actions as per commands"
def execute
thing = Thing.new
instruction_set do |instructions|
instructions.each do |instruction|
command, args = parse_instruction(instruction) # private helper method
if valid_command?(command, args) # private helper method
response = thing.send(command, *args)
puts format(response) if response
end
end
end
end
# ...
no_tasks do
def instruction_set
if options[:filename]
yield File.readlines(options[:filename]).map { |a| a.chomp }
else
puts usage
print "> "
while line = gets
break if line =~ /EXIT/i
yield [line]
print "> "
end
end
end
# ..
end
I've tested successfully for executing commands contained in a file with this code:
spec/cli_spec.rb
describe CLI do
let(:cli) { CLI.new }
subject { cli }
describe "executing instructions from a file" do
let(:default_file) { "instructions.txt" }
let(:output) { capture(:stdout) { cli.execute } }
context "containing valid test data" do
valid_test_data.each do |data|
expected_output = data[:output]
it "should parse the file contents and output a result" do
cli.stub(:options) { { filename: default_file } } # Thor options hash
File.stub(:readlines).with(default_file) do
StringIO.new(data[:input]).map { |a| a.strip.chomp }
end
output.should == expected_output
end
end
end
end
# ...
end
and the valid_test_data referred to above is in the following form:
support/utilities.rb
def valid_test_data
[
{
input: "C1 ARGS\r
C2\r
C3\r
C4",
output: "OUTPUT\n"
}
# ...
]
end
What I want to do now is exactly the same thing but instead of reading each command from the 'file' and executing it, I want to somehow simulate a user typing in to stdin. The code below is utterly wrong, but I hope it can convey the direction I want to go.
spec/cli_spec.rb
# ...
# !!This code is wrong and doesn't work and needs rewriting!!
describe "executing instructions from the command line" do
let(:output) { capture(:stdout) { cli.execute } }
context "with valid commands" do
valid_test_data.each do |data|
let(:expected_output) { data[:output] }
let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } }
it "should process the commands and output the results" do
commands.each do |command|
cli.stub!(:gets) { command }
if command == :report
STDOUT.should_receive(:puts).with(expected_output)
else
STDOUT.should_receive(:puts).with("> ")
end
end
output.should include(expected_output)
end
end
end
end
I've tried using cli.stub(:puts) in various places, and generally rearranging this code around a lot, but can't seem to get any of my stubs to put data in stdin. I don't know if I can parse the set of inputs I expect from the command line in the same way as I do with a file of commands, or what code structure I should be using to solve this issue. If someone who has spec-ed up command-line apps could chime in, that would be great. Thanks.
Rather than stubbing the universe, I think a few bits of indirection would help you write a unit test for this code. The simplest thing you can do is to allow the IO object for output to be injected, and default it to STDOUT:
class CLI < Thor
def initialize(stdout=STDOUT)
#stdout = stdout
end
# ...
#stdout.puts # instead of raw puts
end
Then, in your tests, you can use a StringIO to examine what was printed:
let(:stdout) { StringIO.new }
let(:cli) { CLI.new(stdout) }
Another option is to use Aruba or something like it, and write full-on integration tests, where you actually run your program. This has other challenges as well (such as being nondestructive and making sure not to squash/use system or user files), but will be a better test of your app.
Aruba is Cucumber, but the assertions can use RSPec matchers. Or, you can use Aruba's Ruby API (which is undocumented, but public and works great) to do this without the hassle of Gherkin.
Either way, you will benefit from making your code a bit more test-friendly.
I ended up finding a solution that I think fairly closely mirrors the code for executing instructions from a file. I overcame the main hurdle by finally realizing that I could write cli.stub(:gets).and_return and pass it in the array of commands I wanted to execute (as parameters thanks to the splat * operator), and then pass it the "EXIT" command to finish. So simple, yet so elusive. Many thanks go to this StackOverflow question and answer for pushing me over the line.
Here is the code:
describe "executing instructions from the command line" do
let(:output) { capture(:stdout) { cli.execute } }
context "with valid commands" do
valid_test_data.each do |data|
let(:expected_output) { data[:output] }
let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } }
it "should process the commands and output the results" do
cli.stub(:gets).and_return(*commands, "EXIT")
output.should include(expected_output)
end
end
end
# ...
end
Have you looked at Aruba? It lets you write Cucumber feature tests for command line programs. You can define the input to your CLI that way.
You can write you feature definitions with RSpec, so it's not completely new.
You can stub all Thor::Actions with Rspec's allow_any_instance_of
here is one example:
it "should import list" do
allow_any_instance_of(Thor::Actions).to receive(:yes?).and_return(true)
end

How to share data between implementation and description of a spec?

I wonder if there's any good way to reuse data between implementation and description of a spec... More particularly, I'd like to be able do something like the following:
describe "#some_method" do
let(:arg1) { "Sample String 1" }
let(:arg2) { "Sample String 2" }
context "with '#{arg1}', its result" do
specify { some_method(arg1).should == 1 }
end
context "with '#{arg2}', its result" do
specify { some_method(arg2).should == 2 }
end
end
Of course, this code won't work - arg1 and arg2 are not accessible outside of spec bodies.
Is it possible to achieve the similar result without using global variables or external classes?
Update:
I'm interested in the output of the spec. Something like this:
#some_method
with 'Sample String 1' its result
should == 1
with 'Sample String 2' its result
should == 2
The answer is that you don't use dynamic descriptions. The RSpec way to do this would be
describe "#some_method" do
it "extracts the number correctly" do
some_method("Sample String 1").should == 1
some_method("Sample String 2").should == 2
end
end
It is no problem to hard-code test data in your specs. If you want more complete output, you can use a custom matcher
require 'rspec'
class Test
def some_method(str)
str[/[0-9]+/].to_i
end
end
RSpec::Matchers.define :return_value_for_argument do |result, arg|
match do |actual|
actual.call(arg) == result
end
description do
"return #{result.inspect} for argument #{arg.inspect}"
end
end
describe Test do
let(:test) { Test.new }
describe "#some_method" do
subject { test.method(:some_method) }
it { should return_value_for_argument 1, "str 1" }
end
end
When doing API testing, I find it incredibly useful to be able to see the path, params, and response of each test. I have used the very useful tips given by Jeff Nyman to store things in the example.metatadata[:thing_i_want_to_store_like_url] of each test and with a custom formatter, print it out.
So my tests output look something like this:
that jonathan does not know it exists
:path : /user/20
:params: {}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
that jonathan cannot edit
:path : /user/20/update
:params: {:name=>"evil_name"}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
It's not appropriate to cite specific arguments in your descriptions. Your descriptions should provide a human-readable description of the desired behavior, without reference to specific arguments in most cases.

Resources