Parse looping forever when replacing %% by <!-- --> - parsing

I want to transform
test: "bla bla %bla bla% bla bla bla bla %bla% bla"
into
test: "bla bla <!--bla bla--> bla bla bla bla <!--bla--> bla"
which I thought would be easy as it was a slight variation of red parsing and replacing double % with <> doesn't work
but my code loops forever though I have "to end" rule:
test: "bla bla %bla bla% bla bla bla bla %bla% bla"
toggle: -1
rules: [
any [
to "%" mark: (
toggle: negate toggle
either toggle = 1 [change mark {} insert mark {<!--}][change mark {}
insert mark {-->}]
)
]
|
to end
]
parse test rules
test

I would not use to (or thru), they are bit dangerous IMO. I would use something like:
toggle: false
parse test [
some [
change #"%" (either toggle: not toggle ["<!--"]["-->"])
| skip
]
]
Also, you can get rid of the toggle, if you want to:
parse test [
some [
change #"%" (first reverse ["-->" "<!--"])
| skip
]
]

The problem comes from you trying to exchange "%" with an empty string. "%" stays there and gets always a hit. Your rule works with these modifications
rules: [
any [
to "%" mark: (
toggle: negate toggle
either toggle = 1 [
change/part mark {<!--} 1
][
change/part mark {-->} 1
]
)
]
to end
]

Without using parse, though there is no advantage, just another way:
until [none? attempt [insert remove find test "%" first reverse ["-->" "<!--"]]]

Related

Ruby efficient each loop

Is there a better way to write this? markdown is a StringIO
coverage_hash_arr = [
{
"Module": "Mobile",
"name": "Sheila Chapman",
"age": 21
},
{
"Module": "Web",
"name": "Hendricks Walton",
"age": 40
},
{
"Module": "Misc",
"name": "Torres Mcdonald",
"age": 39
}
]
coverage_hash_arr.each do |the_hash|
    markdown << "------- Status on #{the_hash[:Module]} -------\n"
    the_hash.delete(:Module)
    the_hash.each {|key, value| markdown << "- #{key}: #{value} \n"}
    markdown << "----------------------------------------------\n"
end
I tried this and it seems to work but I wonder if there's a better way (recursion)?
coverage_hash_arr.collect do |the_hash|
the_hash.each do |key,value|
key == :Module ? markdown << "--------- Status for #{value} ----------\n" : markdown << " - #{key}: #{value} \n"
end
markdown << "------------------------------------\n\n"
end
You could:
use puts instead of << to avoid explicit newlines
use center to center the caption horizontally
use map to generate the attribute strings and utilize puts' behavior of printing array elements on separate lines
use without to get a hash without the :Module key
use * to repeat a string
Applied to your code:
markdown = StringIO.new
coverage_hash_arr.each do |hash|
markdown.puts " Status on #{hash[:Module]} ".center(46, '-')
markdown.puts hash.without(:Module).map { |k, v| "- #{k}: #{v}" }
markdown.puts '-' * 46
markdown.puts
end
Output via puts markdown.string:
-------------- Status on Mobile --------------
- name: Sheila Chapman
- age: 21
----------------------------------------------
--------------- Status on Web ----------------
- name: Hendricks Walton
- age: 40
----------------------------------------------
--------------- Status on Misc ---------------
- name: Torres Mcdonald
- age: 39
----------------------------------------------
Note that the above isn't proper Markdown syntax. You might want to change your output to something like this:
### Status on Mobile
- name: Sheila Chapman
- age: 21
### Status on Web
- name: Hendricks Walton
- age: 40
### Status on Misc
- name: Torres Mcdonald
- age: 39
Here's a more streamlined version which has been adapted to be more idiomatic Ruby:
# Define your hashes with keys having consistent case, and omit extraneous
# surrounding quotes unless defining keys like "this-name" which are not
# valid without escaping.
coverage = [
{
module: "Mobile",
name: "Sheila Chapman",
age: 21
},
{
module: "Web",
name: "Hendricks Walton",
age: 40
},
{
module: "Misc",
name: "Torres Mcdonald",
age: 39
}
]
# Iterate over each of these elements...
coverage.each do |entry|
markdown << "------- Status on #{entry[:module]} -------\n"
entry.each do |key, value|
# Skip a particular key
next if (key == :module)
markdown << "- #{key}: #{value} \n"
end
markdown << "----------------------------------------------\n"
end
This can be adapted to have a list of keys to exclude, or the inverse, of having a list of keys to actually print.
There's really nothing wrong with your approach specifically. The major faux-pas committed is in using delete on the data, which mangles it, rendering it useless if you needed to print this again.
It's generally best to try and avoid tampering with the data you're iterating over unless the purpose of that code is clear in its intent to alter it.
It looks like your input data has always the same key/value pairs and I would be more explicit to make it easier to read and to understand what the actual output is:
coverage_hash_arr.each do |hash|
markdown << <<~STRING
------- Status on #{hash[:Module]} -------
- name: #{hash[:name]}
- age: #{hash[:age]}
----------------------------------------------
STRING
end

Parsing a string list with multiple values into JSON

I have about thirty thousand records with a string column that has been stored in the following format, with different keys:
"something: this, this and that, that, other stuff, another: name, another name, last: here"
In rails, I want to change it into a hash like
{
something: [ "this", "this and that", "that" ],
another: [ "name", "another name" ],
last: [ "here" ]
}
Is there a way to do this elegantly? I was thinking of splitting at the colon, then doing a reverse search of the first space.
There are about a hundred ways to solve this. A pretty straightforward one is this:
str = "something: this, this and that, that, other stuff, another: name, another name, last: here"
key = nil
str.scan(/\s*([^,:]+)(:)?\s*/).each_with_object({}) do |(val, colon), hsh|
if colon
key = val.to_sym
hsh[key] = []
else
hsh[key] << val
end
end
# => {
# something: ["this", "this and that", "that", "other stuff"],
# another: ["name", "another name"],
# last: ["here"]
# }
It works by scanning the string with the following regular expression:
/
\s* # any amount of optional whitespace
([^,:]+) # one or more characters that aren't , or : (capture 1)
(:)? # an optional trailing : (capture 2)
\s* # any amount of optional whitespace
/x
Then it iterates over the matches and puts them into a hash. When a match has a trailing colon (capture 2), a new hash key is created with an empty array for a value. Otherwise the value (capture 1) is added to the array for the most recent key.
Or…
A somewhat less straightforward but cleverer approach is to let the RegExp do more work:
MATCH_LIST_ENTRY = /([^:]+):\s*((?:[^,]+(?:,\s*|$))+?)(?=[^:,]+:|$)/
def parse_list2(str)
str.scan(MATCH_LIST_ENTRY).map do |k, vs|
[k.to_sym, vs.split(/,\s*/)]
end.to_h
end
I won't pick apart the RegExp for this one, but it's simpler than it looks. Regexper does a pretty good job of explaining it.
You can see both of these in action on repl.it here: https://repl.it/#jrunning/LongtermMidnightblueAssembler
If str is the string given in the example, the desired hash can be constructed as follows.
str.split(/, *(?=\p{L}+:)/).
each_with_object({}) do |s,h|
k, v = s.split(/: +/)
h[k.to_sym]= v.split(/, */)
end
#=> {:something=>["this", "this and that", "that", "other stuff"],
# :another=>["name", "another name"],
# :last=>["here"]}
Note:
str.split(/, *(?=\p{L}+:)/)
#=> ["something: this, this and that, that, other stuff",
# "another: name, another name",
# "last: here"]
This regular expression reads, "match a comma followed by zero or more spaces, the match to be immediately followed by one or more Unicode letters followed by a colon, (?=\p{L}+:) being a positive lookahead".
elegantly:
result_hash = {}
string.scan(/(?<key>[\w]+(?=:))|(?<value>[\s\w]+(?=(,|\z)))/) do |key,value|
if key.present?
result_hash[key] = []
current_key = key
elsif value.present?
result_hash[current_key] << value.strip
end
end
then jsonize:
json = result_hash.to.json

Parse doesn't work for insertion after last line of some keyword

I want to insert new sentence under last line where keyword is found, but it doesn't work, seems simple at first:
source: {
bla bla bla bla
bla bla bla bla
bla bla keyword bla bla
bla bla keyword bla bla
bla bla keyword bla bla
bla bla bla bla
bla bla bla bla
bla bla bla bla
}
rules: [
some [
thru "keyword" to newline skip
]
mark: ([insert mark "stranger"])
to end
]
parse source rules
Your block evaluates to the same block. You have to use
mark: (insert mark "stranger")
without the block.
And don't use source as source is already defined as a mezzanine function.

Ruby - substituting characters in a string with sequential elements of an array

I have a string:
a = 'bla \n bla \n bla \n'
And an array:
b = ['1', '2', '3']
I want to search through the string, and replace every nth instance of \n with the (n-1)th element from the array, resulting in:
a = 'bla 1 bla 2 bla 3'
What is the simplest way for me to do this?
String#gsub with a block makes short work of this:
a.gsub('\n') { b.shift }
Note that Array#shift modifies the original array. Make a copy of it first (b.dup) if that's a problem.
You can use method sub
a = 'bla \n bla \n bla \n'
b = ['1', '2', '3']
b.each { |i| a.sub!('\n', i) }
#> a
#=> "bla 1 bla 2 bla 3"
Just one more way using String#split and Array#zip
a.split('\n').zip(b).join
#=> "bla 1 bla 2 bla 3"

How to print hash data as { label: "Chrome", data: 2 } in Rails

Now my hash data is print as
{ :label=> "Chrome", :data=> 2}
but i want to print it as
{ label: "Chrome", data: 2}
How to make it work?
If you don't mind quotation marks around keys use to_json
puts hash.to_json
If you do, nothing better comes to my mind than:
puts "{" + hash.map{|k,v| "#{k}: #{v.to_json}"}.join(', ') + "}"

Resources