How do I pass an array in ARGV command line arguments? - ruby

I have the following:
=== My program test.rb
def test (app, download, launch )
for i in 0..(app.length - 1)do
#DO SOMETHING HERE WITH THIS
p app[i].to_s + download[i].to_s + launch[i].to_s
end
end
test (ARGV[0].split(//) , ARGV[2].split(//) ,ARGV[1].split(//))
=== other program "other.rb" that parses and executes test.rb
app = ["fda","asdf"]
down = ["ok","nok"]
la = ["ok","ok"]
system("ruby test.rb #{app} #{down} #{la}")
I would like to print it as something like this:
fda ok ok
asdf no nok
However it is printed like this:
foo
dkk
ano
aok
sk
d
f
How can I pass arrays to the app correctly?

I copied your source and pasted it into two files:
test.rb:
def test (app, download, launch )
for i in 0..(app.length - 1)do
#DO SOMETHING HERE WITH THIS
p app[i].to_s + download[i].to_s + launch[i].to_s
end
end
test (ARGV[0].split(//) , ARGV[2].split(//) ,ARGV[1].split(//))
and test2.rb:
app = ["fda","asdf"]
down = ["ok","nok"]
la = ["ok","ok"]
system("ruby test.rb #{app} #{down} #{la}")
Using Ruby 1.9.2p290, I get this result when I run your code:
ruby test2.rb
test.rb:7: syntax error, unexpected ',', expecting ')'
test (ARGV[0].split(//) , ARGV[2].split(//) ,ARGV[1].split(//))
^
test.rb:7: syntax error, unexpected ',', expecting $end
test (ARGV[0].split(//) , ARGV[2].split(//) ,ARGV[1].split(//))
That tells me you didn't accurately report the problem you're seeing.
Fixing the syntax error returns this:
ruby test2.rb
"[[a"
"fos"
"dkd"
"a,f"
",]"
The output I got is what I would expect from your code, however it doesn't resemble what you said you got, so I doubt your reported output is correct. Please be accurate when you ask for help, both in the code you've written and the output you've seen.
Looking at the data your code is passing shows the arguments are:
0 [fda,
1 asdf]
2 [ok,
3 nok]
4 [ok,
5 ok]
FYI, I got those using this bit of code in place of test.rb:
ARGV.each_with_index do |a, i|
puts "#{i} #{a}"
end
I'd expect that, because system("ruby test.rb #{app} #{down} #{la}") doesn't do what you think it does. Here is what the actual command sent to the sub-shell looks like when system processes it:
test.rb [fda, asdf] [ok, nok] [ok, ok]
Again, that matches what the arguments look like when the app sees them.
To understand what is happening, you need to read the documentation for system:
system([env,] command... [,options]) -> true, false or nil
[...]
command... is one of following forms.
[...]
cmdname, arg1, ... : command name and one or more arguments (no shell)
So, system allows us to send all the parameters separately, which is really what you want. Changing test2.rb to:
app = %w[ fda asdf ]
down = %w[ ok nok ]
la = %w[ ok ok ]
system(
"echo",
"test.rb",
%Q["#{ app.join(',') }"],
%Q["#{ down.join(',') }"],
%Q["#{ la.join(',') }"]
)
gives me this on the command-line:
ruby test2.rb
test.rb "fda,asdf" "ok,nok" "ok,ok"
Which seems a bit more usable. Because of how system works, I can clean it up and reduce that a bit to:
app = %w[ fda asdf ]
down = %w[ ok nok ]
la = %w[ ok ok ]
system(
"ruby", # execute this
"test.rb", # with this script name
app.join(','), down.join(','), la.join(',') # and these parameters
)
Looking at what test.rb sees shows:
ruby test2.rb
0 fda,asdf
1 ok,nok
2 ok,ok
Again, an improvement and closer to what your code expected.
I am not going to finish correcting your code because I suspect this is a homework assignment and not a practical use. Some hints to help you are:
Your use of split isn't correct, nor is it where I would put it.
Your use of for is not idiomatic Ruby. Look at Array.each AND learn why it is preferred instead of for.
Your use of to_s is unnecessary, since you are already dealing with strings and characters.

you could use Thor which is a dsl for creating command line apps

Related

Taking a specific time from command line is not working

I have a program, which calculate many things. While I run the code by ruby code.rb everything is okay. The problem starts, when I want to run it by command line with additional option: ruby code.rb --time 201712121100.
The piece of problematic code is below:
include Options #here I have some options to choose, like --time
def calculate_p(time, mode)
if mode
calculator = calc1
else
calculator = calc2
end
calculate_t(time, calculator)
end
def calculate_t(time, calculator)
date_ymd = time.strftime("%Y%m%d")
time_hm = time.strftime("%H%M")
calculator
.with(date_ymd, time_hm)
.run do |result|
if result.ok?
result.stdout.pop.split.first
else
msgw("Program returned with errors.", :error)
msgw("stdout: %s; stderr: %s" % [result.stdout, result.stderr], :error)
false
end
end
end
time = Options.get('--time')
.andand do |time_op|
msgw('Taking time from command line arguments') do
time_op.pop.andand
end
end || msgw('Calculating time for now.') do
Time.now.utc
end || abort
calc=calculate_p(time, mode)
msgw is just define to print messages.
mode takes true or false values.
I received an error:
"calculate_t: undefined method strftime for "201712121100":String (NoMethodError)"
What am I doing wrong? Why using Time.now.utc is working while giving a specific time is not?
I also checked the solutions from here Rails undefined method `strftime' for "2013-03-06":String
and Date.parse() gives the same error.
The issue is here:
time_op.pop.andand
time_op taken from the command line is a string, and you need a Time instance. The get it, use DateTime#strptime:
DateTime.strptime(time_op.pop, "%Y%m%d%H%M").to_time.andand

Ruby: unexpected ',', expecting keyword_end

Very new to Ruby, unable to see the titular syntax error in this bit of code:
#! /usr/bin/env ruby
require 'sensu-plugin/metric/cli'
class MetricAvailableUpdates < Sensu::Plugin::Metric::CLI::Graphite
option :scheme,
description: 'Metric naming scheme',
long: '--scheme SCHEME',
short: '-s SCHEME',
default: "#{Socket.gethostname}"
def run
# Get the metrics.
output = %x[/usr/lib/update-notifier/apt-check --human-readable]
output_lines = output.split(/(\n)/)
metrics = {}
updates_pattern = " packages can be updated."
updates = output_lines[0].tr(upgrades_pattern, "").to_i
metrics[:available_updates] = updates
security_updates_pattern = " updates are security updates."
security_updates = output_lines[2].tr(security_updates_pattern, "").to_i
metrics[:available_security_updates] = security_updates
# Print them in graphite format.
metrics.each do |k, v|
output [config[:scheme], k].join('.'), v
end
# Done
ok
end
end
I can add the code that precedes this if the syntax error is in fact before this section. Edit: added complete file contents per comment request
The complete error, in case that is useful:
./metrics-available-updates.rb:29: syntax error, unexpected ',', expecting keyword_end
output [config[:scheme], k].join('.'), v
If you play around a bit, you will notice that the syntax error goes away either when you comment out the offending line, or alternatively the line
output = %x[/usr/lib/update-notifier/apt-check --human-readable]
When Ruby parses a file, it needs to guess, whether a symbol denotes a method call, or a variable reference. In this case, output springs into existence as a variable, but further down, you write
output [config[:scheme], k].join('.'), v
which means it suddenly becomes a method call.
I admit that the Ruby lexer should give a more helpful error message....
Add the parentheses
...
metrics.each do |k, v|
output([config[:scheme], k].join('.'), v)
end
...

Execute Rspec from Ruby

I am trying to execute rspec from ruby, and get the status or number of failures from a method or something like that. Actually I am running something like this:
system("rspec 'myfilepath'")
but I only can get the string returned by the function. Is there any way to do this directly using objects?
I think the best way would be using RSpec's configuration and Formatter. This would not involve parsing the IO stream, also gives much richer result customisation programmatically.
RSpec 2:
require 'rspec'
config = RSpec.configuration
# optionally set the console output to colourful
# equivalent to set --color in .rspec file
config.color = true
# using the output to create a formatter
# documentation formatter is one of the default rspec formatter options
json_formatter = RSpec::Core::Formatters::JsonFormatter.new(config.output)
# set up the reporter with this formatter
reporter = RSpec::Core::Reporter.new(json_formatter)
config.instance_variable_set(:#reporter, reporter)
# run the test with rspec runner
# 'my_spec.rb' is the location of the spec file
RSpec::Core::Runner.run(['my_spec.rb'])
Now you can use the json_formatter object to get result and summary of a spec test.
# gets an array of examples executed in this test run
json_formatter.output_hash
An example of output_hash value can be found here:
RSpec 3
require 'rspec'
require 'rspec/core/formatters/json_formatter'
config = RSpec.configuration
formatter = RSpec::Core::Formatters::JsonFormatter.new(config.output_stream)
# create reporter with json formatter
reporter = RSpec::Core::Reporter.new(config)
config.instance_variable_set(:#reporter, reporter)
# internal hack
# api may not be stable, make sure lock down Rspec version
loader = config.send(:formatter_loader)
notifications = loader.send(:notifications_for, RSpec::Core::Formatters::JsonFormatter)
reporter.register_listener(formatter, *notifications)
RSpec::Core::Runner.run(['spec.rb'])
# here's your json hash
p formatter.output_hash
Other Resources
Detailed work through
Gist example
I suggest you to take a look into rspec source code to find out the answer. I think you can start with example_group_runner
Edit: Ok here is the way:
RSpec::Core::Runner::run(options, err, out)
Options - array of directories, err & out - streams. For example
RSpec::Core::Runner.run(['spec', 'another_specs'], $stderr, $stdout)
Your problem is that you're using the Kernel#system method to execute your command, which only returns true or false based on whether or not it can find the command and run it successfully. Instead you want to capture the output of the rspec command. Essentially you want to capture everything that rspec outputs to STDOUT. You can then iterate through the output to find and parse the line which will tell you how many examples were run and how many failures there were.
Something along the following lines:
require 'open3'
stdin, stdout, stderr = Open3.popen3('rspec spec/models/my_crazy_spec.rb')
total_examples = 0
total_failures = 0
stdout.readlines.each do |line|
if line =~ /(\d*) examples, (\d*) failures/
total_examples = $1
total_failures = $2
end
end
puts total_examples
puts total_failures
This should output the number of total examples and number of failures - adapt as needed.
This one prints to console and at the same time captures the message. The formatter.stop is just a stub function, I don't know what it is for normally, I had to include it to use DocumentationFormatter. Also the formatter output contains console coloring codes.
formatter = RSpec::Core::Formatters::DocumentationFormatter.new(StringIO.new)
def formatter.stop(arg1)
end
RSpec.configuration.reporter.register_listener(formatter, :message, :dump_summary, :dump_profile, :stop, :seed, :close, :start, :example_group_started)
RSpec::Core::Runner.run(['test.rb','-fdocumentation'])
puts formatter.output.string

⌃⇧H in TextMate to 'Tidy' HTML causes NoMethodError

I tried using 'Tidy' in an HTML document for the first time yesterday, and got...
/tmp/temp_textmate.Z2P0KX:30:in `<main>': undefined method `empty?' for nil:NilClass (NoMethodError)
I've not done anything to the code in the bundle...
#!/usr/bin/env ruby -wKU
require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb'
require ENV['TM_SUPPORT_PATH'] + '/lib/exit_codes.rb'
result = `"${TM_TIDY:-tidy}" -f /tmp/tm_tidy_errors -iq -utf8 \
-wrap 0 --tab-size $TM_TAB_SIZE --indent-spaces $TM_TAB_SIZE \
--indent yes \
${TM_XHTML:+-asxhtml --output-xhtml yes} \
${TM_SELECTED_TEXT:+--show-body-only yes} \
--enclose-text yes \
--doctype strict \
--wrap-php no \
--tidy-mark no`
status = $?.exitstatus
at_exit { File.unlink('/tmp/tm_tidy_errors') } # Clean up error log
if status == 2 # Errors
msg = "Errors: " + File.read('/tmp/tm_tidy_errors')
TextMate.exit_show_tool_tip msg
elsif status == 1 # Warnings - use output but also display notification with warnings
log = File.read('/tmp/tm_tidy_errors').to_a.select do |line|
! (ENV['TM_SELECTED_TEXT'] and (line.include?('Warning: missing <!DOCTYPE> declaration') or line.include?("Warning: inserting missing 'title' element")))
end.join rescue nil
unless log.empty?
options = {
:title => "Tidy Warnings",
:summary => "Warnings for tidying your document (press escape to close):",
:log => log
}
TextMate::UI.simple_notification(options)
end
end
if ENV['TM_SOFT_TABS'] == "YES"
print result
else
in_pre = false
result.each_line do |line|
unless in_pre
tab_size = ENV["TM_TAB_SIZE"].to_i
space, text = /( *)(.*)/m.match(line)[1..2]
line = "\t" * (space.length / tab_size).floor + " " * (space.length % tab_size) + text
end
print line
in_pre = true if line.include?("<pre>")
in_pre = false if line.include?("</pre>")
end
end
The problem line is unless log.empty?.
I'm running TextMate 1.5.10 (1631) on OS X 10.6.6. I recently installed rvm and upgraded default Ruby to 1.9.2, though forcing TextMate to use 1.8.7 did not fix the problem.
I had the same problem. I have setup my Textmate to use the RVM version of ruby so that I can quickly test scripts.
I solved the problem by unchecking the "TM_RUBY" for the environment variable I had created.
What appears to be happening is the Textmate scripts that wrapper the /usr/bin/tidy command are not executing properly when using a ruby version other than the one that ships with OSX.
I'm curious to see what happens when Lion comes out. Hopefully, Textmate will take another look at these build-in scripts and give them a little "dusting-off".
If you look at the assignment to log, you'll see this:
log = File.read('/tmp/tm_tidy_errors').to_a.select do |line| ... end.join rescue nil
The rescue nil at the end will put a nil into log if the /tmp/tm_tidy_errors file isn't there or it can't be read or what ever. Then the script will call the .empty? method on nil but the nil object has no such method and the script falls over and dies.
You can suppress the problem by changing rescue nil to rescue '' or by changing unless log.empty? to unless log.nil? || log.empty? but that might not be the real problem.
Do you have a TM_TIDY environment variable set? Is there a tidy command in your PATH? Looks like your Tidy install isn't right (or possible not there at all). My OSX has /usr/bin/tidy and apparently that's standard. Try running that big tidy command by hand in a terminal and see what happens.
I had the same problem too, on a machine running OS X 10.9.5 with Ruby upgraded to ruby 2.0.0. Fixed it by taking mu is too short's suggestion to change unless log.empty? to unless long.nil? || log.empty?. That allowed Tidy to run properly, but the top of my HTML selection was still showing me annoying errors:
ruby: warning: -K is specified; it is for 1.8 compatibility and may cause odd behavior
/Applications/TextMate.app/Contents/SharedSupport/Support/lib/ui.rb:129: warning: assigned but unused variable - pid
I shut that up by changing the first line of the script from #!/usr/bin/env ruby -wKU to #!/usr/bin/env ruby -wKU -W0. Obviously the problems are still there under the hood, but for something helpful but not essential, as this functionality is, I think it's plenty good enough.

How do I create a ruby app that I can run commands on

I am building a little tool in ruby for creating directories and files based on commands that I issue it from the command line. I would like for this to work on Mac, Windows, and Linux.
I am of course new to ruby and I know how to right a simple script and call it to run from the command line. What I would like to do is be able to navigate anywhere on my system call the name of the app and pass args so that I can have it create files and directories based in my current location in the command line.
example $> myapp -create mydirectoryname
So what is the best way to do this. Could you guys point me to a resource that walks me through this? Thanks so much.
-Matthew
If you want something standard, See Getoptlong
require 'getoptlong'
opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--repeat', '-n', GetoptLong::REQUIRED_ARGUMENT ],
[ '--name', GetoptLong::OPTIONAL_ARGUMENT ]
)
dir = nil
name = nil
repetitions = 1
opts.each do |opt, arg|
case opt
when '--help'
puts "Help here..."
when '--repeat'
repetitions = arg.to_i
when '--name'
if arg == ''
name = 'John'
else
name = arg
end
end
end
if ARGV.length != 1
puts "Missing dir argument (try --help)"
exit 0
end
dir = ARGV.shift
Dir.chdir(dir)
for i in (1..repetitions)
print "Hello"
if name
print ", #{name}"
end
puts
end
Example command line:
hello -n 6 --name -- /tmp
I personally like trollop, it is not included in the standard library.
Once you have the command line stuff going, see FileUtils module to create the directory:
require 'fileutils'
FileUtils.mkdir("dir")
Getoptlong mentioned by duncan is part of ruby core, but there are much nicer external libraries that let you do it in a cleaner/easier way.
I recommend you look at Choice. The examples given there should be enough to get you going.

Resources