Ruby command line script: Trying to pass variable in switch case - ruby

I'm creating a ruby command line tool which has a switch case statement, I'd like to pass through variables on this switch case statement for example:
input = gets.chomp
case input
when 'help'
display_help
when 'locate x, y' # this is the bit i'm stuck on
find_location(x, y)
when 'disappear s'
disappear_timer(s)
when 'exit'
exit
else
puts "incorrect input"
end
Essentially I want the user to be able to type in locate 54, 30 or sleep 5000 and then call a function which handles the number they passed. I was wondering how I can either pass arguments from the user in a switch statement like this for my command line tool like this?

Use Regexp matcher inside when:
when /locate \d+, \d+/
find_location *input.scan(/\d+/).map(&:to_i)
Here we basically match whatever is locate followed by digits, comma, space, digits. If matched, we extract the digits from the string with String#scan and then convert to Integers, finally passing them as an argument to find_location method.

Related

How is simple math done to only part of stdin?

I am working on a Automator service and in my situation I have stdin as
B-Funny Flash Nonfiction 202105131635 and I want to get to B-Funny Flash Nonfiction 202105131636 incriminating the "5" by 1 to "6".
I'd think I'd first want to separate the text from the number before doing the add 1 then rejoin them?
Would egrep or sed or awk be best?
Tips?
Bash has simple integer arithmetic built in.
str='B-Funny Flash Nonfiction 202105131635'
# parse into prefix and number
num=${str##*[!0-9]}
prefix=${str%$num}
echo "$prefix$((num+1))"
The parameter expansion ${var#pat} produces the value of the variable var with any prefix matching pat removed; % does the same for suffixes, and doubling the operator changes to matching the longest possible pattern match instead of the shortest. The pattern *[!0-9] matches a string which ends on a character which isn't a number; in this context, it retrieves the prefix, i.e. everything up to just before the first digit. (If your prefix could contain numbers, too, this needs tweaking. Probably switch to removing all digits from the end, then extracting the removed numbers; but I guess this will require an unattractive temporary variable.)
Finally, the secret sauce which evaluates an arithmetic expression is the $((...)) arithmetic context.
For more involved number crunching, try bc or Awk. In fact, this could be a one-liner in Awk:
awk '{ $NF +=1 }1' <<<"$str"
The here string passes the value as standard input to Awk, which increments the last field $NF. The final 1 is a common Awk shorthand for "print all input lines to output".
I don't know the bash tools well enough to give a cool one-line answer, so here is a python script instead.
Usage
Save the code in a file increment.py;
Make the file executable with chmod +x increment.py;
Run the script with ./increment.py blablabla 123.
Code
#!/usr/bin/env python3
import sys
def print_help(argv0):
print('increment numbers by 1')
print('example usage:')
print(' {} B-Funny Flash Nonfiction 202105131635'.format(argv0))
print(' B-Funny Flash Nonfiction 202105131636')
def main(argv):
if len(argv) < 2:
print_help(argv[0])
else:
for s in argv[1:]:
if s.isnumeric():
print(int(s) + 1, end=' ')
else:
print(s, end=' ')
print()
if __name__=='__main__':
main(sys.argv)
Explanation
In a python program called from the command-line, the command-line arguments are stored in the array sys.argv.
The first element of the array, with index 0, is the name that was used to call the program, most likely "./increment.py" in our case.
The remaining elements are the parameters that were passed to the program; the words "B-Funny", "Flash", "Nonfiction", "202105131635" in our case.
The for-loop for s in argv[1:]: iterates on the elements of argv, but starting with the element 1 (thus ignoring the element 0). Each of these elements is a string; the method .isnumeric is used to check whether this string represents a number or not. Refer to the documentation on .isnumeric.
If the string is not numeric, we print is as-is. If the string is numeric, we compute the number it represents by calling int(s), then we add 1, and we print the result.
Apart from that, the line if len(argv): checks whether argv contains at least two elements; if it doesn't, that means it only contains its element 0, which is "./increment.py"; in this case, instead of printing the arguments, the script calls the function print_help which explains how to use the program.
Finally, the bit about if __name__ == '__main__': is a python idiom to check whether the file increment.py was run as a program or as a module imported by another file. Refer to this question.

How to write a Ruby switch statement (case...when) with regex and backreferences?

I know that I can write a Ruby case statement to check a match against a regular expressions.
However, I'd like to use the match data in my return statement. Something like this semi-pseudocode:
foo = "10/10/2011"
case foo
when /^([0-9][0-9])/
print "the month is #{match[1]}"
else
print "something else"
end
How can I achieve that?
Thanks!
Just a note: I understand that I wouldn't ever use a switch statement for a simple case as above, but that is only one example. In reality, what I am trying to achieve is the matching of many potential regular expressions for a date that can be written in various ways, and then parsing it with Ruby's Date class accordingly.
The references to the latest regex matching groups are always stored in pseudo variables $1 to $9:
case foo
when /^([0-9][0-9])/
print "the month is #{$1}"
else
print "something else"
end
You can also use the $LAST_MATCH_INFO pseudo variable to get at the whole MatchData object. This can be useful when using named captures:
case foo
when /^(?<number>[0-9][0-9])/
print "the month is #{$LAST_MATCH_INFO['number']}"
else
print "something else"
end
Here's an alternative approach that gets you the same result but doesn't use a switch. If you put your regular expressions in an array, you could do something like this:
res = [ /pat1/, /pat2/, ... ]
m = nil
res.find { |re| m = foo.match(re) }
# Do what you will with `m` now.
Declaring m outside the block allows it to still be available after find is done with the block and find will stop as soon as the block returns a true value so you get the same shortcutting behavior that a switch gives you. This gives you the full MatchData if you need it (perhaps you want to use named capture groups in your regexes) and nicely separates your regexes from your search logic (which may or may not yield clearer code), you could even load your regexes from a config file or choose which set of them you wanted at run time.

Why doesn't this Ruby replace regex work as expected?

Consider the following string which is a C fragment in a file:
strcat(errbuf,errbuftemp);
I want to replace errbuf (but not errbuftemp) with the prefix G-> plus errbuf. To do that successfully, I check the character after and the character before errbuf to see if it's in a list of approved characters and then I perform the replace.
I created the following Ruby file:
line = " strcat(errbuf,errbuftemp);"
item = "errbuf"
puts line.gsub(/([ \t\n\r(),\[\]]{1})#{item}([ \t\n\r(),\[\]]{1})/, "#{$1}G\->#{item}#{$2}")
Expected result:
strcat(G->errbuf,errbuftemp);
Actual result
strcatG->errbuferrbuftemp);
Basically, the matched characters before and after errbuf are not reinserted back with the replace expression.
Anyone can point out what I'm doing wrong?
Because you must use syntax gsub(/.../){"...#{$1}...#{$2}..."} or gsub(/.../,'...\1...\2...').
Here was the same problem: werid, same expression yield different value when excuting two times in irb
The problem is that the variable $1 is interpolated into the argument string before gsub is run, meaning that the previous value of $1 is what the symbol gets replaced with. You can replace the second argument with '\1 ?' to get the intended effect. (Chuck)
I think part of the problem is the use of gsub() instead of sub().
Here's two alternates:
str = 'strcat(errbuf,errbuftemp);'
str.sub(/\w+,/) { |s| 'G->' + s } # => "strcat(G->errbuf,errbuftemp);"
str.sub(/\((\w+)\b/, '(G->\1') # => "strcat(G->errbuf,errbuftemp);"

Ruby regexp to parse command line

How can I parse strings in ruby like many command line utilities do? I've got strings similar to "command [--opt1=...] [--enable-opt2] --opt3=... arg1" and methods similar to command(opt1,opt2,opt3,arg1...). I want to let arguments to come in random order, some of them can be optional.
At the moment I wrilte regexp every time I need to parse new command, as for example
to parse "lastpost --chan=your_CHANNEL /section/"
I have this regular expression:
text = "lastpost --chan=0chan.ru /s/"
command = (text.match /^\w+/)[0]
args = text.gsub(/^\w+/,'')
if args =~ /[[:blank:]]*(--chan\=([[:graph:]]+)[[:blank:]]+)*\/?(\w+)\/?/
chan = $2
section = $3
do_command(chan,section)
else
puts "wrong args"
end
I wish i had create_regexp(opts,args), which should produce regular expression.
Ok, I found optparse can do it for me

Ruby: String Comparison Issues

I'm currently learning Ruby, and am enjoying most everything except a small string comparason issue.
answer = gets()
if (answer == "M")
print("Please enter how many numbers you'd like to multiply: ")
elsif (answer. == "A")
print("Please enter how many numbers you'd like to sum: ")
else
print("Invalid answer.")
print("\n")
return 0
end
What I'm doing is I'm using gets() to test whether the user wants to multiply their input or add it (I've tested both functions; they work), which I later get with some more input functions and float translations (which also work).
What happens is that I enter A and I get "Invalid answer."The same happens with M.
What is happening here? (I've also used .eql? (sp), that returns bubcus as well)
gets returns the entire string entered, including the newline, so when they type "M" and press enter the string you get back is "M\n". To get rid of the trailing newline, use String#chomp, i.e replace your first line with answer = gets.chomp.
The issue is that Ruby is including the carriage return in the value.
Change your first line to:
answer = gets().strip
And your script will run as expected.
Also, you should use puts instead of two print statements as puts auto adds the newline character.
your answer is getting returned with a carriage return appended. So input "A" is never equal to "A", but "A(return)"
You can see this if you change your reject line to print("Invalid answer.[#{answer}]"). You could also change your comparison to if (answer.chomp == ..)
I've never used gets put I think if you hit enter your variable answer will probably contain the '\n' try calling .chomp to remove it.
Add a newline when you check your answer...
answer == "M\n"
answer == "A\n"
Or chomp your string first: answer = gets.chomp

Resources