How to find a specific word in string with Ruby/Rails - ruby-on-rails

I got a few string like so:
TFjyg9780878_867978-DGB097908-78679iuhi698_widesky_light_87689uiyhk
AND
TFjyg9780878_867978-DGB097908-78679iuhi698_sky_light_87689uiyhk
AND
TFjyg9780878_867978-DGB097908-78679iuhi698_widesky_dark_87689uiyhk
AND
TFjyg9780878_867978-DGB097908-78679iuhi698_sky_dark_87689uiyhk
I need to check whether the strings above has one of the widesky_light, sky_light, widesky_dark and sky_dark with exactitude so I wrote this:
if my_string.match("widesky_light")
...
end
For each variant, but the problem I'm having is because sky_light and widesky_light are similar, my code is not working properly. I believe the solution to the above would be a regex, but I've spend the afternoon yesterday trying to get it to work without much success.
Any suggestions?
EDIT
A caveat: in this string (as example): TFjyg9780878_867978-DGB097908-78679iuhi698_widesky_light_87689uiyhk, the part after widesky_light, which is _87689uiyhk is optional, meaning that sometimes I have it, sometimes I don't, so a solution would not be able to count on _string_.

Looks like you just need to reorder your if statements
if my_string.match(/widesky_light/)
return 'something'
end
if my_string.match(/sky_light/)
return 'something'
end

Regex
1st regex : extract word for further checking
Here's a regex which only matches the interesting part :
(?<=_)[a-z_]+(?=(?:_|\b))
It means lowercase word with possible underscore inside, between 2 underscores or after 1 underscore and before a word boundary.
If you need some logic depending on the case (widesky, sky, light or dark), you could use this solution.
Here in action.
2nd regex : direct check if one of 4 words is present
If you just want to know if any of the 4 cases is present :
(?<=_)(?:wide)?sky_(?:dark|light)(?=(?:_|\b))
Here in action, with either _something_after or nothing.
Case statement
list = %w(
TFjyg9780878_867978-DGB097908-78679iuhi698_widesky_light_87689uiyhk
TFjyg9780878_867978-DGB097908-78679iuhi698_sky_light_87689uiyhk
TFjyg9780878_867978-DGB097908-78679iuhi698_widesky_dark_87689uiyhk
TFjyg9780878_867978-DGB097908-78679iuhi698_sky_dark_87689uiyhk
TFjyg9780878_867978-DGB097908-78679iuhi698_trash_dark_87689uiyhk
)
list.each do |string|
case string
when /widesky_light/ then puts "widesky light found!"
when /sky_light/ then puts "sky light found!"
when /widesky_dark/ then puts "widesky dark found!"
when /sky_dark/ then puts "sky dark found!"
else puts "Nothing found!"
end
end
In this order, the case statement should be fine. widesky_dark won't match twice, for example.

Maybe something like this:
case my_string
when /_(sky_light)/
# just sky_light
when /sky_light/
# widesky_light
when /_(sky_dark)/
# just sky_dark
when /sky_dark/
# widesky_dark
else
puts "I don't like"
end

Related

How to detect if a field contains a character in Lua

I'm trying to modify an existing lua script that cleans up subtitle data in Aegisub.
I want to add the ability to delete lines that contain the symbol "♪"
Here is the code I want to modify:
-- delete commented or empty lines
function noemptycom(subs,sel)
progress("Deleting commented/empty lines")
noecom_sel={}
for s=#sel,1,-1 do
line=subs[sel[s]]
if line.comment or line.text=="" then
for z,i in ipairs(noecom_sel) do noecom_sel[z]=i-1 end
subs.delete(sel[s])
else
table.insert(noecom_sel,sel[s])
end
end
return noecom_sel
end
I really have no idea what I'm doing here, but I know a little SQL and LUA apparently uses the IN keyword as well, so I tried modifying the IF line to this
if line.text in (♪) then
Needless to say, it didn't work. Is there a simple way to do this in LUA? I've seen some threads about the string.match() & string.find() functions, but I wouldn't know where to start trying to put that code together. What's the easiest way for someone with zero knowledge of Lua?
in is only used in the generic for loop. Your if line.text in (♪) then is no valid Lua syntax.
Something like
if line.comment or line.text == "" or line.text:find("\u{266A}") then
Should work.
In Lua every string have the string functions as methods attached.
So use gsub() on your string variable in loop like...
('Text with ♪ sign in text'):gsub('(♪)','note')
...thats replace the sign and output is...
Text with note sign in text
...instead of replacing it with 'note' an empty '' deletes it.
gsub() is returning 2 values.
First: The string with or without changes
Second: A number that tells how often the pattern matches
So second return value can be used for conditions or success.
( 0 stands for "pattern not found" )
So lets check above with...
local str,rc=('Text with strange ♪ sign in text'):gsub('(♪)','notation')
if rc~=0 then
print('Replaced ',rc,'times, changed to: ',str)
end
-- output
-- Replaced 1 times, changed to: Text with strange notation sign in text
And finally only detect, no change made...
local str,rc=('Text with strange ♪ sign in text'):gsub('(♪)','%1')
if rc~=0 then
print('Found ',rc,'times, Text is: ',str)
end
-- output is...
-- Found 1 times, Text is: Text with strange ♪ sign in text
The %1 holds what '(♪)' found.
So ♪ is replaced with ♪.
And only rc is used as a condition for further handling.

Multiple `gsub` in multiple `each` loops getting overriden one by another

Trying to iterate over some phrases, and whenever I find a word, I need to replace it with a link.
phrases = ["hello world", "worldwide"]
words_to_link = ["world", "world"]
I am trying to get:
"hello <a href='world'>world</a><br />worldwide"
My code is:
phrases.each do |ph|
words_to_link.each do |w|
ph.gsub!(w, "<a href='#{w}'>#{w}</a>")
end
end.join("<br />").html_safe
The output of this is:
"hello <a href='<a href='world'>world</a>'><a href='world'>world</a></a><br /><a href='<a href='world'>world</a>'><a href='world'>world</a></a>wide"
On the first run it finds all occurrences of world, but on the second, it goes inside the generated world and gsubs again.
Another problem is the proper regex to only find words by boundaries, I thought it would be /\b(word)\b, but that didn't work.
Any pointers?
I'm a little confused by your question, so may have got the wrong end of the stick here. However, here is an answer by my interpretation:
phrases = ["hello world", "worldwide"]
substitutions = { /\bworld\b/ => "world" }
phrases.each do |ph|
substitutions.each do |pattern, replacement|
ph.gsub!(pattern, "<a href='#{replacement}'>#{replacement}</a>")
end
end
phrases.join("<br />").html_safe
You can use \b in a regex to mark a work boundary, to avoid altering the "worldwide" string. And (I think this is what you wanted?) you can define some mapping between the search/replace terms rather than looping though twice, to avoid the double-replacement.

Is there a more elegant way to perform a multiple include? selection in ruby?

I came across this issue in a project last night, while writing a helper to pick an icon depending on a file extension, and was wondering if there was a better (more "ruby") way of handling it?
The code currently goes something like:
def choose_icon(myfile)
icon = "default-icon"
if ["*.doc","*.docx",".txt","*.dot"].include? File.extname(myfile)
icon = "doc-icon"
end
if ["*.xls","*.xlsx","*.xlt"].include? File.extname(myfile)
icon = "sheet-icon"
end
if ["*.mp3","*.aac","*.aiff","*.wav"].include? File.extname(myfile)
icon = "audio-icon"
end
if ["*.mov","*.m4a","*.wmv"].include? File.extname(myfile)
icon = "movie-icon"
end
icon # return the chosen icon
end
This somehow feels a little clumsy and inelegant to me, and I was struggling to find a better way to do it in Ruby.
(Note: The above example is REALLY simplified and the actual code is far longer and looks far more untidy.)
It would be really cool if the 'case' construct would work like this:
def choose_icon(myfile)
case File.extname(myfile)
when ["*.doc","*.docx",".txt","*.dot"].include?
"doc-icon"
when ["*.xls","*.xlsx","*.xlt"].include?
"sheet-icon"
when ["*.mp3","*.aac","*.aiff","*.wav"].include?
"audio-icon"
when ["*.mov","*.m4a","*.wmv"].include?
"movie-icon"
else
"default-icon"
end
end
But of course, that doesn't work. It is so much easier to read though, so I was wondering if I had missed some other method of doing a multiple comparison against a collection of options that would restore some elegance and readability to my code?
You almost got it right. Just leave off the square brackets and the include? and it will work. I think the asterisks should not be necessary either since File.extname returns the extension with just a dot.
def choose_icon(myfile)
case File.extname(myfile)
when '.doc', '.docx', '.txt', '.dot'
'doc-icon'
when '.xls', '.xlsx', '.xlt'
'sheet-icon'
when '.mp3', '.aac', '.aiff', '.wav'
'audio-icon'
when '.mov', '.m4a', '.wmv'
'movie-icon'
else
'default-icon'
end
end
You could use a hash:
h = [*(%w| .doc .docx .txt .dot |).product(["doc-icon"]),
*(%w| .xls .xlsx .xlt |).product(["sheet-icon"]),
*(%w| .aac .aiff .wav |).product(["audio-icon"]),
*(%w| .mov .m4a .wmv |).product(["movie-icon"])].to_h
#=> {".doc"=>"default-icon", ".docx"=>"default-icon",
# ".txt"=>"default-icon", ".dot"=>"default-icon",
# ".xls"=>"sheet-icon" , ".xlsx"=>"sheet-icon",
# ".xlt"=>"sheet-icon" , ".aac"=>"audio-icon",
# ".aiff"=>"audio-icon" , ".wav"=>"audio-icon",
# ".mov"=>"movie-icon" , ".m4a"=>"movie-icon",
# ".wmv"=>"movie-icon"}
h.default = "default-icon"
h[File.extname("myfile.wav")]
#=> "audio-icon"
h[File.extname("./mydir/myfile.abc")]
#=> "default-icon"

If else or case statement help!

I could really use some help with the best way to accomplish the following:
I have the below in my controller (and I know this should not be here and needs to move to the model)
This is an email messaging system so according to what position you hold you are able to email out to set groups of people. So if you are Battalion Commander etc you can choose to message out to one of the 5 groups defined below. If you are a Company Commander your groups change. In the view there is a drop down menu and you choose the group your message goes out to. The select menu is populated depending on the position of the signed in user.
The problem seems that the "elsif" portion does not populate the message correctly. It shows the right drop down list and acts as if the email is sent but the emails are not being populated. However the first value (Battalion Commander) works fine.
Do I have something written incorrectly in the if else statement? It seems like it should be pretty simple. The user position always reflects correctly so that is not it.
if (#position == "Battalion Commander" or "Command Sergeant Major" or "FRSA" or "Battalion FRG Leader")
#bnok = (#user.battalion.primaries).collect(&:email).select{|s| !s.blank?}.join(", ")
#bspouses = (#user.battalion.primaries(:conditions => ["relationship = 'spouse'"])).collect(&:email).select{|s| !s.blank?}.join(", ")
#bsoldiers= (#user.battalion.soldiers).collect(&:email).select{|s| !s.blank?}.join(", ")
#bsoldierspouse=((#user.battalion.soldiers)+(#user.battalion.primaries(:conditions => ["relationship = 'spouse'"]))).collect(&:email).select{|s| !s.blank?}.join(", ")
#ballcontacts=((#user.battalion.soldiers)+(#user.battalion.primaries)+(#user.battalion.additionals)).collect(&:email).select{|s| !s.blank?}.join(", ")
elsif
#position == ("Company Commander" or "First Sergeant" or "FRG Leader")
#nok = (#user.company.primaries).collect(&:email).select{|s| !s.blank?}.join(", ")
#spouses = (#user.company.primaries(:conditions => ["relationship = 'spouse'"])).collect(&:email).select{|s| !s.blank?}.join(", ")
#soldiers= (#user.company.soldiers).collect(&:email).select{|s| !s.blank?}.join(", ")
#soldierspouse=((#user.company.soldiers)+(#user.company.primaries(:conditions => ["relationship = 'spouse'"]))).collect(&:email).select{|s| !s.blank?}.join(", ")
#allcontacts=((#user.company.soldiers)+(#user.company.primaries)+(#user.company.additionals)).collect(&:email).select{|s| !s.blank?}.join(", ")
end
So this does not work, it work for either one set of positions or the other but not both. This correlates to a select menu in the view and depending on what position you hold the query on certain groups of people change.
So in the view I have this:
<% if #position == "Battalion Commander" %>
<%= f.select(:bcc_email, [["Please Select", ""], ["Battalion NOK", #bnok], ["Battalion Spouses", #bspouses], ["Battalion Soldiers Only", #bsoldiers], ["Battalion Soldiers & Spouses", #bsoldierspouse], ["All Battalion Contacts", #ballcontacts]]) %></h1><br />
I am still learning rails and I am not sure if a case statement would be better but then I am confused on where that goes and how that case statement fits into the view.
Any guidance would be great, I am trying to chip away at this and figure it out, but I would really appreciate some help.
Firstly it may help if you can format that a bit clearer - for us and yourself, often simple formatting will help identify issues.
Secondly, what is your goal here? I gather it's some sort of war simulator or something? And assume you realise that in an if statement, only one of them will be executed:
if (xxx)
aaaaaaaaaaaaaaaaaa
elseif (yyyy)
bbbbbbbbbbbbbb
end
in the case that xxx is true, the aaaaaaaa will be executed, and then it'll jump to end. The bbbbbbbbb part will not be executed - even if true, because the first statement was. If however xxx is not true, yyyy will be evaluated, and if true, bbbbbbbbbbbbb will happen.
I hope that helps a bit?
disclaimer: I don't know ruby. I have been told it is similar to python and LISP in some regards, so this answer makes that assumption.
What I would do is maintain these condition variables in a dictionary (or map, or hash table, whatever your language calls it). The "rank" of the person would be a key, and the value would correspond to the function you want to execute for that rank. for instance,
#the following example is python-esque. you'll have to port it to ruby.
def some_funct1(): #pass parameters if you need to. i don't, here.
sql_stuff_here
def some_funct2():
sql_stuff_here
map['important dude'] = some_funct1;
map['another important dude'] = some_funct1;
map['unimportant dude'] = some_funct2;
map['another_unimportant dude'] = some_funct2;
#after some time, you have a person whose rank is rank.
map[rank]() #this will execute the function
You wrote
if (... a big test ...)
..do some work..
elsif
..do something else..
end
but that is wrong! You should just write else instead of elsif unless you need to check another condition.
[I assume elsif followed by nothing=nil is always false]

Truncate Markdown?

I have a Rails site, where the content is written in markdown. I wish to display a snippet of each, with a "Read more.." link.
How do I go about this? Simple truncating the raw text will not work, for example..
>> "This is an [example](http://example.com)"[0..25]
=> "This is an [example](http:"
Ideally I want to allow the author to (optionally) insert a marker to specify what to use as the "snippet", if not it would take 250 words, and append "..." - for example..
This article is an example of something or other.
This segment will be used as the snippet on the index page.
^^^^^^^^^^^^^^^
This text will be visible once clicking the "Read more.." link
The marker could be thought of like an EOF marker (which can be ignored when displaying the full document)
I am using maruku for the Markdown processing (RedCloth is very biased towards Textile, BlueCloth is extremely buggy, and I wanted a native-Ruby parser which ruled out peg-markdown and RDiscount)
Alternatively (since the Markdown is translated to HTML anyway) truncating the HTML correctly would be an option - although it would be preferable to not markdown() the entire document, just to get the first few lines.
So, the options I can think of are (in order of preference)..
Add a "truncate" option to the maruku parser, which will only parse the first x words, or till the "excerpt" marker.
Write/find a parser-agnostic Markdown truncate'r
Write/find an intelligent HTML truncating function
Write/find an intelligent HTML truncating function
The following from http://mikeburnscoder.wordpress.com/2006/11/11/truncating-html-in-ruby/, with some modifications will correctly truncate HTML, and easily allow appending a string before the closing tags.
>> puts "<p><b>Something</p>".truncate_html(5, at_end = "...")
=> <p><b>Someth...</b></p>
The modified code:
require 'rexml/parsers/pullparser'
class String
def truncate_html(len = 30, at_end = nil)
p = REXML::Parsers::PullParser.new(self)
tags = []
new_len = len
results = ''
while p.has_next? && new_len > 0
p_e = p.pull
case p_e.event_type
when :start_element
tags.push p_e[0]
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
when :end_element
results << "</#{tags.pop}>"
when :text
results << p_e[0][0..new_len]
new_len -= p_e[0].length
else
results << "<!-- #{p_e.inspect} -->"
end
end
if at_end
results << "..."
end
tags.reverse.each do |tag|
results << "</#{tag}>"
end
results
end
private
def attrs_to_s(attrs)
if attrs.empty?
''
else
' ' + attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ')
end
end
end
Here's a solution that works for me with Textile.
Convert it to HTML
Truncate it.
Remove any HTML tags that got cut in half with
html_string.gsub(/<[^>]*$/, "")
Then, uses Hpricot to clean it up and close unclosed tags
html_string = Hpricot( html_string ).to_s
I do this in a helper, and with caching there's no performance issue.
You could use a regular expression to find a line consisting of nothing but "^" characters:
markdown_string = <<-eos
This article is an example of something or other.
This segment will be used as the snippet on the index page.
^^^^^^^^^^^^^^^
This text will be visible once clicking the "Read more.." link
eos
preview = markdown_string[0...(markdown_string =~ /^\^+$/)]
puts preview
Rather than trying to truncate the text, why not have 2 input boxes, one for the "opening blurb" and one for the main "guts". That way your authors will know exactly what is being show when without having to rely on some sort of funkly EOF marker.
I will have to agree with the "two inputs" approach, and the content writer would need not to worry, since you can modify the background logic to mix the two inputs in one when showing the full content.
full_content = input1 + input2 // perhaps with some complementary html, for a better formatting
Not sure if it applies to this case, but adding the solution below for the sake of completeness. You can use strip_tags method if you are truncating Markdown-rendered contents:
truncate(strip_tags(markdown(article.contents)), length: 50)
Sourced from:
http://devblog.boonecommunitynetwork.com/rails-and-markdown/
A simpler option that just works:
truncate(markdown(item.description), length: 100, escape: false)

Resources