Redmine plugin that replaces words via regular expression? - ruby

I'm very new to Redmine/Ruby trying to achieve a simple plugin that takes the current wiki page content and matches/replaces everytime a word occurs via regular expression. How can I do this?
Thanks!
Dennis

The word replacement can de done by using gsub() with \b to match a word boundary:
irb(main):001:0> 'foo bar baz foo bar'.gsub /\bfoo\b/, 'replaced'
=> "replaced bar baz replaced bar"
Here is a more complete solution with a dictionary of words to replace:
repl = {'foo'=>'apple', 'baz'=>'banana'}
s = 'foo bar baz foo bar'
for from, to in repl:
s = s.gsub /\b#{from}\b/, to
end
Result: apple bar banana apple bar

Related

Replace text in file if previous line matches another text

My file looks like this:
FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = bar
FooBarC
foo bar
foo = bar
...
What I would like to do is to write a script that replaces the bar in foo = bar but only if it belongs to FooBarB. So in the example above only the second bar out of all foo = bar lines should be replaced.
I've played around with sed but I just can't get it done right. I would also like to avoid installing any tools that aren't necessarily pre-installed on the system (I'm on Mac OS), since the script will be used by other team members too.
One way to do it with sed (tested using macOS's sed and GNU sed), would be this:
replace.sed
#!/usr/bin/env sed -Ef
/FooBarB/,/^FooBar/ {
s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/
}
Here's what it does:
/FooBarB/,/^FooBar/ matches a range of lines where the first line matches the regex /FooBarB/ and the last line matches the regex /^FooBar/ (which is the start of the next "group"). The comma between the two regexes is the syntax for range matching in sed.
s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/ — [s]ubstitutes (in the matched range of lines) whatever matches the regex (foo[[:space:]]*=[[:space:]]*).+ with \1new-value, where \1 references the first capturing group in the search regex. The search regex looks for foo followed by optional whitespace, followed by an = sign, followed again by whitespace and then whatever else is there, which in your case is the old value.
You could do it all in just one line, but I wanted to show a version that's a bit more digestible (as far as sed goes, in any case):
sed -E '/FooBarA/,/^FooBar/s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/' temp.md
This might work for you (GNU sed):
sed '/FooBarB/{:a;n;/^$/b;/foo = bar/!ba;s//foo = baz/}' file
Match on the string FooBarB and start a loop.
Fetch the next line and study it.
If the line is empty the stanza is done, so break out of the loop.
If the line does not contains the string foo = bar, fetch the next line and continue the loop.
Otherwise, substitute the new value for bar and finish the loop.
Alternative (which may work for macos users?):
sed -e '/FooBarB/{:a' -e 'n;/^$/b;/foo = bar/!ba;s//foo = baz/;}' file
Since the OP changed the input data to the question another solution:
sed '/FooBar/h;G;/FooBarB/s/foo = bar/foo = baz/;P;d' file
Using any awk in any shell on every Unix box:
$ awk -v tgt='FooBarB' -v val='whatever' '
NF==1{tag=$0} (NF>1) && (tag==tgt) && sub(/=.*/,"= "){$0=$0 val}
1' file
FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = whatever
FooBarC
foo bar
foo = bar
For reference, the GNU awk variant:
awk -v v="newvalue" 'BEGIN{FS=OFS="\n";RS=ORS="\n\n"}$1=="FooBarB"{$3="foo = " v}1' file
By using the option -v, the variable v holds the wanted string.
The BEGIN statement sets respectively the input, output field separator, the input and output record separator to one and two carriage return.
That way a record is composed of the block of several lines containing the pattern Foobar[ABC].
The last statement sets the new value by rewriting the third line.

Display Unique Shell Columns

Given we have two formatted strings that are unrelated to each other.
#test.rb
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
How can I use ruby or call shell commands to have each of these string display as columns in terminal? The key is that the data of each string are not building a correlated row, ie this is not a table, rather 2 lists side by side.
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
You can try using paste and column command together. Note that this is a shell command so spaces between the assignment operator should be corrected.
$ string_1="Title\nfoo bar\nbaz\nfoo bar baz boo"
$ string_2="Unrelated Title\ndog cat farm\nspace moon"
$ paste -d '|' <(echo -e "$string_1") <(echo -e "$string_2") | column -s'|' -t
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
We paste the lines with | as delimiter and tell column command to use | as a reference to form columns.
In Ruby, you could do it this way:
#!/usr/bin/env ruby
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
a1 = string_1.split("\n")
a2 = string_2.split("\n")
a1.zip(a2).each { |pair| puts "%-20s%s" % [pair.first, pair.last] }
# or
# a1.zip(a2).each { |left, right| puts "%-20s%s" % [left, right] }
This produces:
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
Hi , If you Use temp files
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
echo -e $string_1 >a.txt
echo -e $string_2 >b.txt
paste a.txt b.txt
I hope it will help.

Ruby is returning actual newlines instead of \n

I have a file "Foo.md", which contains three lines:
Foo
Bar
I want File.read("Foo.md") to return "Foo\n\nBar" It does this when I run it from irb in the Terminal, but when I run it from a script such as
content = File.read('Foo.md')
puts content
it returns with the lines converted to actual returns. I need that variable as a single line for what comes next in my script.
To be clear: I'm not interested in changing the number of lines, just debugging to make sure the variable content is being passed as a single line.
You are still reading "Foo\n\n\nBar". However, puts interprets the special characters.
You can use String#inspect:
puts content.inspect # => "Foo\n\n\nBar"
str =
"Foo
Bar"
#=> "Foo\n\n\nBar"
You could also do this:
str.gsub(/\n{3,}/,"\n\n")
#=> "Foo\\nnBar"
It might help you visualize what is happening by meditating on this:
str = <<EOT
Foo
Bar
EOT
str's contents look like:
str # => "Foo\n\n\nBar\n"
Inspecting it escapes the backslashes for a visual representation, but it's NOT what you'd want to use when creating a string with embedded line-ends. You'd want to define it as it's shown above.
str.inspect # => "\"Foo\\n\\n\\nBar\\n\""
inspect output varies when viewed in IRB vs. the console. Here's what you'd see in the console:
ruby -e 'puts "\n"; puts "\n".inspect'
"\n"
Printing str to the console:
puts str
# >> Foo
# >>
# >>
# >> Bar

Ruby regex- matching text if it doesn't start with a character

I was wondering how you are able to match a pattern only if it doesn't start with a specific character. I would like to match "foo" but I would not like to match "afoo." What kind of regex operator do I need for that? I can't seem to find the right one. Maybe an anchor?
For example I'd like to change
foo foo afoo foo
to
bar bar afoo bar
Thanks.
Although the answer below is correct for my example, what about if it was /foo instead of afoo? That doesn't seem to behave the same?
Sounds like you're looking for a negative look behind. If you say (?<!expr1)expr2 then it will match whatever expr2 matches as long as it isn't immediately preceded by something expr1 matches. For example:
>> 'foo foo afoo foo'.gsub(/(?<!a)foo/, 'bar')
=> "bar bar afoo bar"
str = "foo foo afoo foo"
str.gsub(/\bfoo/, "bar") #=> "bar bar afoo bar"

Bash: How do I create a variable containing a list of arguments with multi-word tokens?

I want to create a variable which is essentially a list of arguments to pass to a command or function. The pattern below with arg_string works well for foo, bar, and baz, but not for "multi word token", which I would like the command to see as a single argument.
#!/bin/bash
function func() {
for arg in "$#"
do
echo ${arg}
done
}
arg_string="foo bar baz \"multi word token\""
arg_string="foo bar baz multi\ word\ token"
arg_string="foo bar baz 'multi word token'"
func ${arg_string}
Here is the output:
foo
bar
baz
'multi
word
token'
When I want:
foo
bar
baz
multi word token
Just stick an eval before the function call:
eval func ${arg_string}

Resources