# Easily parsing a string to retrieve values and assign them t

**URL:** <https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718>\
**Category:** Ruby\
**Created:** [July 18, 2007, 9:51am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718 "2007-07-18T09:51:56Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 9:51am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/1 "2007-07-18T09:51:56Z")

</div>

Hi!

I’ve been looking in API’s for a while in desperate need for an easy  
way to parse string and retrieve data (forget about Regexp or scanf),  
so that any non-rubyist guy I work with could describe, with a single  
string, a FTP directory on which some files are saved. Moreover, I  
need some metadata so that I can effectively sort and work with data I  
retrieve from this FTP.

For example, I would not know which file I should retrieve on:  
‘ftp://ftp.org/DATA/mike’  
but  
‘ftp://ftp.org/DATA/{user\_name}/{year}/{month}-{day}.txt’ would do  
just fine, so that I could, for example, get this hash:  
{:year=\>“2005”, :user\_name=\>“mike”, :day=\>“15”, :month=\>“10”}  
for this filename:  
‘ftp://ftp.org/DATA/mike/2005/10-15.txt’

I don’t know if such a method is already available for Ruby, so I  
decided to implement it on my own. Here it is:

###### Source

class String  
def  
parse\_for\_variables(description,begin\_var\_name="{",end\_var\_name="}")  
split\_reg\_exp=Regexp.new(Regexp.quote(begin\_var\_name)\<\<"(.+?)"\<\<Regexp.quote(end\_var\_name))  
@variables=[]  
@is\_a\_variable\_name=true  
searching\_reg\_exp=Regexp.new("^"\<\<description.split(split\_reg\_exp).collect{|str|  
@is\_a\_variable\_name=!@is\_a\_variable\_name  
if @is\_a\_variable\_name then  
@variables\<\<str.sub(/:(\d+)$/,’’).intern  
str=~/:(\d+)$/ ? ‘(.{’\<\<$1\<\<’})’ :"(.+)"  
else  
Regexp.quote(str)  
end  
}.join\<\<"$")  
values=searching\_reg\_exp.match(self).to\_a[1…-1]

!values.nil? &&  
@variables.length==values.length &&  
Hash.check\_for\_consistency\_and\_create\_from\_arrays(@variables,values)  
end  
end

class Hash  
def self.create\_from\_arrays(keys,values)  
self[\*keys.zip(values).flatten]  
end

def self.check\_for\_consistency\_and\_create\_from\_arrays(keys,values)  
@result={}  
keys.each\_with\_index{|k,i|  
raise ArgumentError if @result.has\_key?(k) and  
@result[k]!=values[i]  
@result[k]=values[i]  
}  
@result  
rescue ArgumentError  
false  
end  
end

############################################################

#### Examples

irb(main):026:0\> ‘foobar’.parse\_for\_variables(‘foo{name}’)  
=\> {:name=\>“bar”}

# You can specify the length of a string by adding :i to the end of a

variable name

irb(main):027:0\> ‘foobar’.parse\_for\_variables(‘foo{name:3}’)  
=\> {:name=\>“bar”}

irb(main):028:0\> ‘foobar’.parse\_for\_variables(‘foo{name:2}’)  
=\> false

irb(main):029:0\> ‘foobar’.parse\_for\_variables(‘foo{name}’)  
=\> {:name=\>“bar”}

# By default, variable names are written between {}, but it could be

overridden with optional arguments

irb(main):030:0\> ‘foo(bar){|x|  
x+2}’.parse\_for\_variables(‘foo(\<\>){|\<\>|  
\<\>}’,’\<\<’,’\>\>’)  
=\> {:arg=\>“bar”, :var=\>“x”, :expression=\>“x+2”}

irb(main):031:0\>  
‘C:\Windows\system32\vbrun700.dll’.parse\_for\_variables(’{disk}:{path}{filename}.{extension}’)  
=\> {:disk=\>“C”, :extension=\>“dll”, :filename=\>“vbrun700”,  
:path=\>“Windows\system32”}

irb(main):032:0\>  
‘2006-12-09.csv’.parse\_for\_variables(’{year}-{month}-{day}.csv’)  
=\> {:year=\>“2006”, :day=\>“09”, :month=\>“12”}

irb(main):033:0\> ‘2005 12 15’.parse\_for\_variables(’{year} {month}  
{day}’)  
=\> {:year=\>“2005”, :day=\>“15”, :month=\>“12”}

irb(main):034:0\>  
‘20061209.txt’.parse\_for\_variables(’{year:4}{month:2}{day:2}.txt’)  
=\> {:year=\>“2006”, :day=\>“09”, :month=\>“12”}

irb(main):035:0\>  
‘20061209.txt’.parse\_for\_variables(’{year:2}{month:2}{day:2}.txt’)  
=\> false

# You can use a variable name twice:

irb(main):036:0\>  
‘DATA/2007/2007-12-09.csv’.parse\_for\_variables(‘DATA/{year}/{year}-{month}-{day}.csv’)  
=\> {:year=\>“2007”, :day=\>“09”, :month=\>“12”}

# as long as values are consistent:

irb(main):037:0\>  
‘DATA/2007/2006-12-09.csv’.parse\_for\_variables(‘DATA/{year}/{year}-{month}-{day}.csv’)  
=\> false

irb(main):038:0\>  
‘whateverTooLong’.parse\_for\_variables(‘whatever{name:4}’)  
=\> false

irb(main):039:0\>  
‘whateverAsLongAsIWant’.parse\_for\_variables(‘whateverKsome\_variableK’,‘K’,‘K’)  
=\> {:some\_variable=\>“AsLongAsIWant”}

irb(main):040:0\>  
‘whatevertoolong.csv’.parse\_for\_variables(‘whatever$name:4$.csv’,’$’,’$’)  
=\> false  
############################################################

Have you ever use such a method?  
Is it possible to implement it in a more elegant way?

Thanks for reading, and please feel free to use my code if you ever need  
it,

Eric D.

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 9:57am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/2 "2007-07-18T09:57:48Z")

</div>

Eric DUMINIL wrote:

> ‘20061209.txt’.parse\_for\_variables(’{year:4}{month:2}{day:2}.txt’)  
> =\> {:year=\>“2006”, :day=\>“09”, :month=\>“12”}

I like this. It’s sort of like a cut down regex for non-programmers. You  
should write this up with a definition and put it in a library. I bet  
people would use it.

Don’t forget to come up with a cool name.

best,  
Dan

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 10:12am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/3 "2007-07-18T10:12:57Z")

</div>

From: Eric DUMINIL [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]

# For example, I would not know which file I should retrieve on:

# ‘ftp://ftp.org/DATA/mike’

# but

# ‘ftp://ftp.org/DATA/{user\_name}/{year}/{month}-{day}.txt’ would do

# just fine, so that I could, for example, get this hash:

# {:year=\>“2005”, :user\_name=\>“mike”, :day=\>“15”, :month=\>“10”}

# for this filename:

# ‘ftp://ftp.org/DATA/mike/2005/10-15.txt’

very nice.  
but would it be more practical if we delineate a variable just like we  
used to in ruby inline string; ie, use #{var} instead of just {var}

this would be handy like, if i want to rename or move all folders under  
/mike/2005/ to /mike/2007/ eg… the retrieval and assignment string just  
stay the same…

kind regards -botp

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 10:36am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/4 "2007-07-18T10:36:03Z")

</div>

Hi  
Thanks for the appreciation!  
Your suggestion is interesting, even though I’m not sure it would work,  
because:

‘foobar’.parse\_for\_variables(‘foo#{name}’,’#{’)  
=\> {:name=\>“bar”}

works, but when you use it with double quotes string:

‘foobar’.parse\_for\_variables(“foo#{name}”,’#{’)  
NameError: undefined local variable or method `name’ for main:Object

it already tries to evaluate “name” inside the string…  
so either you get retrieval or assignment right, but not both ☹  
Anyway, assignment is not that big a deal:

(irb) h={:year=\>“2005”, :user\_name=\>“mike”, :day=\>“15”, :month=\>“10”}  
=\> {:year=\>“2005”, :user\_name=\>“mike”, :day=\>“15”, :month=\>“10”}

(irb)  
‘ftp://ftp.org/DATA/{user\_name}/{year}/{month}-{day}.txt’.gsub(/{(.+?)}/){h[$1.intern]}  
=\> “ftp://ftp.org/DATA/mike/2005/10-15.txt”

Best regards,

Eric

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 11:16am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/5 "2007-07-18T11:16:09Z")

</div>

From: Eric DUMINIL [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]

# ‘foobar’.parse\_for\_variables(“foo#{name}”,‘#{’)

# NameError: undefined local variable or method `name’ for main:Object

oops, totally ignored that, was thinking about lazy evals…  
i think you’re current interface is good, it would be easy to infix the  
“#” later…

kind regards -botp

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 11:25am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/6 "2007-07-18T11:25:12Z")

</div>

There is an option for regexen to lazily evaluate. So you could  
represent a regex-free string with a regex like that, then whenever  
you need it - evaluate the regex, convert it to a string and use it  
:).

OR you could store the string ‘stuff #{name}’ and later #eval() it or  
something similar and less dangerous when you need it’s evaluation.

Aur

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [July 18, 2007, 11:38am UTC](https://www.ruby-forum.com/t/easily-parsing-a-string-to-retrieve-values-and-assign-them-t/109718/7 "2007-07-18T11:38:13Z")

</div>

I think that what you describe is exactly what I implemented as  
searching\_reg\_exp.

For example searching\_reg\_exp corresponding to  
‘ftp://ftp.org/DATA/{user\_name}/{year}/{month}-{day}.txt’ is:  
/^ftp://ftp.org/DATA/(.+)/(.+)/(.+)-(.+).txt$/

if you want it to be non-greedy, it would be:  
/^ftp://ftp.org/DATA/(.+?)/(.+?)/(.+?)-(.+?).txt$/

Or did I get you wrong?

I wouldn’t choose the eval() path for security reasons, as you mentioned  
it…  
‘foo{system(“rm -rf ~/”)}’ would be pretty bad!

Which method are you thinking about when you wrote “something similar  
and less dangerous”?

Bye,

Eric
