When I use the following code:
+----------------------+---------------+---------------------------------------------------------------------+
| A | B | C |
+======================+===============+=====================================================================+
| Merchant Rating | Ad Extension | Star ratings plus number of reviews for the advertiser/merchant. |
| | | |
| | |.. image:: /images/merchant-rating.png |
+----------------------+---------------+---------------------------------------------------------------------+
The text preceding the image in column C gets wrapped in <blockquote> tags in the HTML output. Is there any way to avoid this?
To avoid the blockquote tag in the first paragraph of the third column, you could try using this:
+----------------------+---------------+---------------------------------------------------------------------+
| A | B | C |
+======================+===============+=====================================================================+
| Merchant Rating | Ad Extension | Star ratings plus number of reviews for the advertiser/merchant. |
| | | |
| | | |img| |
+----------------------+---------------+---------------------------------------------------------------------+
.. |img| image:: /images/merchant-rating.png
Instead, you'll get two paragraphs.
Use a substitution and remove the separating line so that Sphinx interprets the content as a single block of text.
+-----------------+--------------+------------------------------------------------------------------+
| A | B | C |
+=================+==============+==================================================================+
| Merchant Rating | Ad Extension | Star ratings plus number of reviews for the advertiser/merchant. |
| | | |img| |
+-----------------+--------------+------------------------------------------------------------------+
.. |img| image:: /images/merchant-rating.png
I need to tell Ruby in regex to split before and after the + - * / symbols in my program.
Examples:
I need to turn "1+12" into [1.0, "+", 12.0]
and "6/0.25" into [6.0, "/", 0.25]
There could be cases like "3/0.125" but highly unlikely. If first two I listed above are satisfied it should be good.
On the Ruby docs, "hi mom".split(%r{\s*}) #=> ["h", "i", "m", "o", "m"]
I looked up a cheat-sheet to try to understand %r{\s*}, and I know that the stuff inside %r{} such as \s are skipped and \s means white space in regex.
'1.0+23.7'.scan(/(((\d\.?)+)|[\+\-\*\/])/)
instead of splitting, match with capture groups to parse your inputs:
(?<operand1>(?:\d+(?:\.\d+)?)|(?:\.\d+))\s*(?<operator>[+\/*-])\s*(?<operand2>(?:\d+(?:\.\d+)?)|(?:\.\d+))
explanation:
I've used named groups (?<groupName>regex) but they aren't necessary and could just be ()'s - either way, the sub-captures will still be available as 1,2,and 3. Also note the (?:regex) constructs that are for grouping only and do not "remember" anything, and won't mess up your captures)
(?:\d+(?:\.\d+)?)|(?:\.\d+)) first number: either leading digit(s) followed optionally by a decimal point and digit(s), OR a leading decimal point followed by digit(s)
\s* zero or more spaces in between
[+\/*-] operator: character class meaning a plus, division sign, minus, or multiply.
\s* zero or more spaces in between
(?:\d+(?:\.\d+)?)|(?:\.\d+) second number: same pattern as first number.
regex demo output:
I arrived a little late to this party, and found that many of the good answers had already been taken. So, I set out to expand on the theme slightly and compare the performance and robustness of each of the solutions. It seemed like a fun way to entertain myself this morning.
In addition to the 3 examples given in the question, I added test cases for each of the four operators, as well as for some new edge cases. These edge cases included handling of negative numbers and arbitrary spaces between operands, as well as how each of the algorithms handled expected failures.
The answers revolved around 3 methods: split, scan, and match. I also wrote new solutions using each of these 3 methods, specifically respecting the additional edge cases that I added to here. I ran all of the algorithms against this full set of test cases, and ended up with a table of pass/fail results.
Next, I created a benchmark that created 1,000,000 test strings that each of the solutions would be able to parse properly, and ran each solution against that sample set.
On first benchmarking, Cary Swoveland's solution performed far better than the others, but didn't pass the added test cases. I made very minor changes to his solution to produce a solution that supported both negative numbers and arbitrary spaces, and included that test as Swoveland+.
The final results printed from to the console are here (note: horizontal scroll to see all results):
| Test Case | match | match | scan | scan |partition| split | split | split | split |
| | Gaskill | sweaver | Gaskill | techbio |Swoveland| Gaskill |Swoveland|Swoveland+| Lilue |
|------------------------------------------------------------------------------------------------------|
| "1+12" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "6/0.25" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "3/0.125" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "30-6" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "3*8" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "20--4" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "33+-9" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "-12*-2" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "-72/-3" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "34 - 10" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| " 15+ 9" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "4*6 " | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "b+0.5" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "8---0.5" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "8+6+10" | Pass | -- | Pass | -- | -- | -- | -- | -- | -- |
| "15*x" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "1.A^ff" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
============================================================
user system total real
match (Gaskill): 4.770000 0.090000 4.860000 ( 5.214996)
match (sweaver2112): 4.640000 0.040000 4.680000 ( 4.911849)
scan (Gaskill): 7.360000 0.080000 7.440000 ( 7.719646)
scan (techbio): 12.930000 0.140000 13.070000 ( 13.791613)
partition (Swoveland): 5.390000 0.050000 5.440000 ( 5.648762)
split (Gaskill): 5.150000 0.100000 5.250000 ( 5.455094)
split (Swoveland): 3.860000 0.060000 3.920000 ( 4.040774)
split (Swoveland+): 4.240000 0.040000 4.280000 ( 4.537570)
split (Lilue): 7.540000 0.090000 7.630000 ( 8.022252)
In order to keep this post from being far too long, I've included the complete code for this test at https://gist.github.com/mgaskill/96f04e7e1f72a86446f4939ac690759a
The robustness test cases can be found in the first table above. The Swoveland+ solution is:
f,op,l = formula.split(/\b\s*([+\/*-])\s*/)
return [f.to_f, op, l.to_f]
This includes a \b metacharacter prior to splitting on an operator ensures that the previous character is a word character, giving support for negative numbers in the second operand. The \s* metacharacter expressions support arbitrary spaces between operands and operator. These changes incur less than 10% performance overhead for the additional robustness.
The solutions that I provided are here:
def match_gaskill(formula)
return [] unless (match = formula.match(/^\s*(-?\d+(?:\.\d+)?)\s*([+\/*-])\s*(-?\d+(?:\.\d+)?)\s*$/))
return [match[1].to_f, match[2], match[3].to_f]
end
def scan_gaskill(formula)
return [] unless (match = formula.scan(/^\s*(-?\d+(?:\.\d+)?)\s*([+*\/-])\s*(-?\d+(?:\.\d+)?)\s*$/))[0]
return [match[0][0].to_f, match[0][1], match[0][2].to_f]
end
def split_gaskill(formula)
match = formula.split(/(-?\d+(?:\.\d+)?)\s*([+\/*-])\s*(-?\d+(?:\.\d+)?)/)
return [match[1].to_f, match[2], match[3].to_f]
end
The match and scan solutions are very similar, but perform significantly differently, which is very interesting, because they use the exact same regex to do the work. The split solution is slightly simpler, and only splits on the entire expression, capturing each operand and the operator, separately.
Note that none of the split solutions was able to properly identify failures. Adding this support requires additional parsing of the operands, which significantly increases the overhead of the solution, typically running about 3 times slower.
For both performance and robustness, match is the clear winner. If robustness isn't a concern, but performance is, use split. On the other hand, scan provided complete robustness, but was more than 50% slower than the equivalent match solution.
Also note that using an efficient way to extract the results from the solution into the result array is as important to performance as is the algorithm chosen. The technique of capturing the results array into multiple variables (used in Woveland) outperformed the map solutions dramatically. Early testing showed that the map extraction solution more than doubled the runtimes of even the highest-performing solutions, hence the exceptionally high runtime numbers for Lilue.
I think this could be useful:
"1.2+3.453".split('+').flat_map{|elem| [elem, "+"]}[0...-1]
# => ["1.2", "+", "3.453"]
"1.2+3.453".split('+').flat_map{|elem| [elem.to_f, "+"]}[0...-1]
# => [1.2, "+", 3.453]
Obviously this work only for +. But you can change the split character.
EDIT:
This version work for every operator
"1.2+3.453".split(%r{(\+|\-|\/|\*)}).map do |x|
unless x =~ /(\+|\-|\/|\*)/ then x.to_f else x end
end
# => [1.2, "+", 3.453]
R = /
(?<=\d) # match a digit in a positive lookbehind
[^\d\.] # match any character other than a digit or period
/x # free-spacing regex definition mode
def split_it(str)
f,op,l = str.delete(' ').partition(R)
[convert(f), op, convert(l)]
end
def convert(str)
(str =~ /\./) ? str.to_f : str.to_i
end
split_it "1+12"
#=> [1, "+", 12]
split_it "3/ 5.2"
#=> [3, "/", 5.2]
split_it "-4.1 * 6"
#=> [-4.1, "*", 6]
split_it "-8/-2"
#=> [-8, "/", -2]
The regex can of course be written in the conventional way:
R = /(?<=\d)[^\d\.]/
So my issues might be of syntactic nature, maybe not, but I am clueless on how to proceed next. I am writing a test case on the Robot Framework, and my end goal is to be able to run ,multiple tests, back to back in a Loop.
In this cases below, the Log to Console call works fine, and outputs the different values passed as parameters. The next call "Query Database And Analyse Data" works as well.
*** Test Cases ***
| For-Loop-Elements
| | #{Items} = | Create List | ${120} | ${240} | ${240}
| | :FOR | ${ELEMENT} | IN | #{ITEMS}
| | | Log To Console | Running tests at Voltage: ${ELEMENT}
| | | Query Database And Analyse Data
But then, when I try to makes a test cases with documentation and tags with "Query Database And Analyse Data", I get the Error: Keyword Name cannot be Empty, which leads me to think that when the file gets to [Documentation tag], it doesn't understand that it is part of a test case. This is usually how I write test cases.
Please note here that the indentation tries to match the inside of the loop
*** Test Cases ***
| For-Loop-Elements
| | #{Items} = | Create List | ${120} | ${240} | ${240}
| | :FOR | ${ELEMENT} | IN | #{ITEMS}
| | | Log To Console | Running tests at Voltage: ${ELEMENT}
| | | Query Database And Analyse Data
| | | | [Documentation] | Query DB.
| | | | [Tags] | query | voltagevariation
| | | Duplicates Test
| | | | [Documentation] | Packets should be unique.
| | | | [Tags] | packet_duplicates | system
| | | | Duplicates
| | | Chroma Output ON
| | | | [Documentation] | Setting output terminal status to ON
| | | | [Tags] | set_output_on | voltagevariation
| | | | ${chroma-status} = | Chroma Output On | ${HOST} | ${PORT}
Now is this a syntax problem, indentation issue, or is it just plain impossible to do what I'm trying to do? If you have written similar cases, but in a different manner, please let me know!
Any help or input would be highly appreciated!
You are trying to use Keywords as Test Cases. This approach is not supported by Robot Framework.
What you could do is make one Test Case with a lot of Keywords:
*** Test Cases ***
| For-Loop-Elements
| | #{Items} = | Create List | ${120} | ${240} | ${240}
| | :FOR | ${ELEMENT} | IN | #{ITEMS}
| | | Log To Console | Running tests at Voltage: ${ELEMENT}
| | | Query Database And Analyse Data
| | | Duplicates
| | | ${chroma-status} = | Chroma Output On | ${HOST} | ${PORT}
*** Keywords ***
| Query Database And Analyse Data
| | Do something
| | Do something else
...
You can't really fit [Tags] anywhere useful. You can, however, fire meaningful fail messages (substituting the [Documentation]) if instead of using a Keyword directly you wrapped it in Run Keyword And Return Status.
Furthermore, please have a look at data driven tests to get rid of the :FOR-loop completely.
In my scenario outline I have the below
Examples:
| user | password | from | to | amount | date | message |
| joel10 | lolpw12 | bankA | bankB | $100 | 1/30/2015 | Transfer Success. |
in my step definitions I have the below
And(/^the user inputs fields (.*), (.*), (.*)$/) do |from, to, amount|
on(TransferPage).from = /#{from}/
on(TransferPage).to = /#{to}/
on(TransferPage).amount = /#{amount}/
on(TransferPage).date = /#{date}/
end
The FROM, TO, and AMOUNT all comes out correct from the table but when it inputs the date, it comes out (?-mix:1/30/2015)
why is this happening and how do i fix?
When you do /#{date}/ you are taking the value returned from the parsing of the step definition and then turning it into a regular expression:
/#{date}/.class
#=> Regexp
You presumably want to leave the value in its original String format:
on(TransferPage).date = date
I am using a scenario table (multiline step arguments) to check some data from a screen using cucumber, using the in built .diff! method on the Cucumber AST table.
I would like to check the content matches against regular expressions.
Scenario: One
Then the table appears as:
| One | Two | Three |
| /\d+/ | /\d+/ | /\d+/ |
The actual table could look something like
| One | Two | Three |
| 123 | 456 | 789 |
which this scenario is translated to "as long as there are some digits, I don't care"
An example step implementation that fails:
Then /^the table appears as:$/ do |expected_table|
actual_table = [['One','Two', 'Three'],['123', '456', '789']]
expected_table.diff! actual_table
end
Error:
Then the table appears as: # features/step_definitions/my_steps.rb:230
| One | Two | Three |
| /\\d+/ | /\\d+/ | /\\d+/ |
| 123 | 456 | 789 |
Tables were not identical (Cucumber::Ast::Table::Different)
I have tried using step transforms to transform the cells into regular expressions, but they still aren't identical.
Transform code:
expected_table.raw[0].each do |column|
expected_table.map_column! column do |cell|
if cell.respond_to? :start_with?
if cell.start_with? "/"
cell.to_regexp
else
cell
end
else
cell
end
end
end
which provides the eror:
Then the table appears as: # features/step_definitions/my_steps.rb:228
| One | Two | Three |
| (?-mix:\\d+) | (?-mix:\\d+) | (?-mix:\\d+) |
| 123 | 456 | 789 |
Tables were not identical (Cucumber::Ast::Table::Different)
Any ideas? I am stuck.
Using regular expressions in a scenario is almost certainly the wrong approach. Cucumber features are intended to be read and understood by business-focussed stakeholders.
How about writing the step at a higher level, such as as:
Then the first three columns of the table should contain a digit
There is no way to do it without writing your own implementation of diff! method from Ast::Table. Take a look into cucumber/lib/ast/table.rb. Internally it uses diff-lcs library to do an actual comparison which doesn't support regex match.
It seems that you want to write this in a way that provides the cool diff output. Otherwise, I'd look at writing this such that you simply check the rows. It won't be as pretty, and it won't get you the diff of the entire table, but it's something.
Then /^the table appears as:$/ do |expected_table|
actual_table = [['One','Two', 'Three'],['123', '456', '789']]
expected_table.raw.each_with_index { |row, y|
row.each_with_index { |cell, x|
actual_table[x][y].should == cell
}
}
end