Forum: Ruby on Rails How to let a user render a partial?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
David T. (Guest)
on 2008-10-08 21:12
Hi all,

I'm currently doing a little CMS system in Rails and I would like the
user/administrator of a site to be able to render partials on pages. I
have a Page model with a content attribute. Inside that attribute I
would like the admin to be able to put something like this:

{contact_form submit:"Submit now!" reset:"Reset form"}

This is just an example, but this should then render a partial like
this:

render :partial => "widgets/contact_form", :locals => {:submit =>
"Submit now!", :reset => "Reset form"}

I have been trying to make up a regular expression to approach this, but
I'm not very good at it. It looks like this:

/\{(\w+) (\w+):(.+)*\}/

But it is hard to make it respect multiple arguments, if you know what I
mean. Now, if any of you have an idea of how to finish this regular
expression, or maybe know a different solution of how to let the admin
render partials "dynamically", I would appreciate if you'd let me know.
Frederick C. (Guest)
on 2008-10-08 21:42
(Received via mailing list)
I'd have a look a string scanner if I were you
Fred

Sent from my iPhone

On 8 Oct 2008, at 18:12, David T. <rails-mailing-list@andreas-
David T. (Guest)
on 2008-10-08 22:00
Frederick C. wrote:

> I'd have a look a string scanner if I were you
> Fred
>
> Sent from my iPhone

That's exactly what I've been thinking of, but the real challenge is to
get the regular expression right. It needs to respect multiple
arguments. Any suggestions?
Frederick C. (Guest)
on 2008-10-08 23:09
(Received via mailing list)
On 8 Oct 2008, at 19:00, David T. wrote:

> get the regular expression right. It needs to respect multiple
> arguments. Any suggestions?

that's the point - with stringscanner you don't need to come up with
one giant regular expression that gets everything right

as a quick example, this

require 'strscan'
s = StringScanner.new('blah {some_partial arg1:foo arg2:bar} other
stuff here {otherpartial arg1:baz}')

while s.scan_until /\{/
   puts "start of call"
   puts s.scan /\w+/
   s.skip /\s+/
   while  output=s.scan( /\w+:\w+/)
     puts output
     s.skip /\s+/
   end
   s.skip_until /\}/
   puts "end of call"
end

outputs

start of call
some_partial
arg1:foo
arg2:bar
end of call
start of call
otherpartial
arg1:baz
end of call


Fred
David T. (Guest)
on 2008-10-09 10:10
Frederick C. wrote:

>> get the regular expression right. It needs to respect multiple
>> arguments. Any suggestions?
>
> that's the point - with stringscanner you don't need to come up with
> one giant regular expression that gets everything right

Aha! I thought you meant the RegExp#scan method.

> as a quick example, this
>
> require 'strscan'
> s = StringScanner.new('blah {some_partial arg1:foo arg2:bar} other
> stuff here {otherpartial arg1:baz}')
>
> while s.scan_until /\{/
>    puts "start of call"
>    puts s.scan /\w+/
>    s.skip /\s+/
>    while  output=s.scan( /\w+:\w+/)
>      puts output
>      s.skip /\s+/
>    end
>    s.skip_until /\}/
>    puts "end of call"
> end
>
> outputs
>
> start of call
> some_partial
> arg1:foo
> arg2:bar
> end of call
> start of call
> otherpartial
> arg1:baz
> end of call

This looks just great! I'll return when I've written a complete method.
David T. (Guest)
on 2008-10-09 10:53
David T. wrote:

>> as a quick example, this
>>
>> require 'strscan'
>> s = StringScanner.new('blah {some_partial arg1:foo arg2:bar} other
>> stuff here {otherpartial arg1:baz}')
>>
>> while s.scan_until /\{/
>>    puts "start of call"
>>    puts s.scan /\w+/
>>    s.skip /\s+/
>>    while  output=s.scan( /\w+:\w+/)
>>      puts output
>>      s.skip /\s+/
>>    end
>>    s.skip_until /\}/
>>    puts "end of call"
>> end

...

> This looks just great! I'll return when I've written a complete method.

Okay, I've been taking a look at this, and I'm facing a problem. When
I've scanned a string I want to actually replace all the {some_partial
foo:bar}'s with partial renderings.

I've been looking through the StringScanner documentation
(http://www.ruby-doc.org/core/classes/StringScanner.html), but it
doesn't seem to cover the topic of replacing matches instead of just
extracting it, if you know what I mean.

How can I replace a {some_partial foo:bar} match with a partial
rendering?
Frederick C. (Guest)
on 2008-10-09 11:04
(Received via mailing list)
On Oct 9, 7:53 am, David T. <removed_email_address@domain.invalid>
wrote:
> David T. wrote:
> I've been looking through the StringScanner documentation
> (http://www.ruby-doc.org/core/classes/StringScanner.html), but it
> doesn't seem to cover the topic of replacing matches instead of just
> extracting it, if you know what I mean.
>
> How can I replace a {some_partial foo:bar} match with a partial
> rendering?

the string scanner will tell you its position. Keep track of the
relevant positions and then use String's usual methods to replace
content between 2 positions.

Fred
David T. (Guest)
on 2008-10-09 12:39
Frederick C. wrote:

>> How can I replace a {some_partial foo:bar} match with a partial
>> rendering?
>
> the string scanner will tell you its position. Keep track of the
> relevant positions and then use String's usual methods to replace
> content between 2 positions.

Got it. Okay, I have made this method:

def dropify(content)
  # create a StringScanner
  s = StringScanner.new(content)
  while s.scan_until /\{/
    # where are we now?
    from = s.pointer - 1
    # locals to pass to the partial
    arguments = {}
    partial = s.scan /\w+/
    s.skip /\s+/
    while argument = s.scan( /\w+:\w+/)
      # splitting the argument up into a name and a value
      argument.split!(":")
      name = argument.first.to_sym
      value = argument.last
      # putting it into the arguments hash
      arguments[name] = value
      s.skip /\s+/
    end
    # where are we now?
    to = s.pointer+1
    s.skip_until /\}/
    # put the partial on top of the {some_partial foo:bar} match
    content[from, to] = render :partial => "drops/#{partial}", :locals
=> arguments
  end
  # return
  content
end

Let me start out by saying that both the argument, name, value,
arguments, to and from has the values they should have when I debug
them, but for some reason I'm getting an error that looks like this:

compile error
C:/.../app/views/drops/_contact_form.html.erb:-11: syntax error,
unexpected tIDENTIFIER, expecting ']'
bar=foos = local_assigns[:bar=foo]
                                  ^
C:/.../app/views/drops/_contact_form.html.erb:-7: syntax error,
unexpected tASSOC, expecting kEND
bar=>foos = local_assigns[:bar=>foo]
     ^
C:/.../app/views/drops/_contact_form.html.erb:-5: syntax error,
unexpected tIDENTIFIER, expecting ']'
foo=bar = local_assigns[:foo=bar]

Do anyone know what this means?
Frederick C. (Guest)
on 2008-10-09 12:51
(Received via mailing list)
On Oct 9, 9:39 am, David T. <removed_email_address@domain.invalid>
wrote:
> def dropify(content)
>       # splitting the argument up into a name and a value
>     # put the partial on top of the {some_partial foo:bar} match
>     content[from, to] = render :partial => "drops/#{partial}", :locals
> => arguments
>   end
>   # return
>   content
> end
>
> Let me start out by saying that both the argument, name, value,
> arguments, to and from has the values they should have when I debug
> them, but for some reason I'm getting an error that looks like this:

A general point - it may not be advisable to modify the string while
stringscanner is iterating over it

At least on my version of ruby String#split! doesn't exist, and the
errors suggest it didn't actually do what you think it did (and I
struggle to see how it could exist)
name, value = arguments.split(/:/) would probably work better.

The error you get later is a reflection of the fact that the keys in
your locals are things like bar=foo (and i imagine the values are
blank)

Fred
David T. (Guest)
on 2008-10-09 13:15
Frederick C. wrote:

>> Let me start out by saying that both the argument, name, value,
>> arguments, to and from has the values they should have when I debug
>> them, but for some reason I'm getting an error that looks like this:

> A general point - it may not be advisable to modify the string while
> stringscanner is iterating over it

I don't see other possibilities.

> At least on my version of ruby String#split! doesn't exist, and the
> errors suggest it didn't actually do what you think it did (and I
> struggle to see how it could exist)
> name, value = arguments.split(/:/) would probably work better.

I see, that is corrected now.

> The error you get later is a reflection of the fact that the keys in
> your locals are things like bar=foo (and i imagine the values are
> blank)

Unfortunately that is not true. When I comment out the "content[from,
to] = render..." line and put "puts debug arguments" on the line before
so it looks like this:

...
s.skip_until /\}/
puts debug arguments
#content[from, to] = render :partial => "drops/#{partial}", :locals =>
arguments
...

I get output similar to this:

---
    foo: bars
    bar: foos

I assume this means that the arguments hash is correctly formatted. But
I'm getting the error when the "content[from, to] = render..." line is
NOT commented out, anyway.
Frederick C. (Guest)
on 2008-10-09 13:34
(Received via mailing list)
On 9 Oct 2008, at 10:15, David T. wrote:

> I don't see other possibilities.
accumulate the replacements you need and do them at the end (last one
first)
>> your locals are things like bar=foo (and i imagine the values are
>> blank)
>
> Unfortunately that is not true. When I comment out the "content[from,
> to] = render..." line and put "puts debug arguments" on the line
> before
> so it looks like this:
>
given how :locals is handled it seems highly likely
>    foo: bars
>    bar: foos
>
It would be a lot simpler to just stick a breakpoint in there (or
output arguments.inspect)
David T. (Guest)
on 2008-10-09 14:16
Frederick C. wrote:
> On 9 Oct 2008, at 10:15, David T. wrote:
>
>> I don't see other possibilities.
> accumulate the replacements you need and do them at the end (last one
> first)
>>> your locals are things like bar=foo (and i imagine the values are
>>> blank)
>>
>> Unfortunately that is not true. When I comment out the "content[from,
>> to] = render..." line and put "puts debug arguments" on the line
>> before
>> so it looks like this:
>>
> given how :locals is handled it seems highly likely
>>    foo: bars
>>    bar: foos
>>
> It would be a lot simpler to just stick a breakpoint in there (or
> output arguments.inspect)

I'm quite close to the solution now, my method now looks like this:

def dropify(content)
  s = StringScanner.new(content)
  drops = {}
  i = 0
  while s.scan_until /\{/
    drop = {}
    drop[:from] = s.pointer - 1
    drop[:arguments] = {}
    drop[:partial] = s.scan /\w+/
    s.skip /\s+/
    while argument = s.scan( /\w+:\w+/)
      name, value = argument.split(/:/)
      drop[:arguments][name.to_sym] = value
      s.skip /\s+/
    end
    drop[:to] = s.pointer+1
    s.skip_until /\}/
    i = i+1
    drops[i] = drop
  end
  puts drops.inspect
  drops.each do |drop|
    #content[drop[:from], drop[:to]] = "...Partial content..."
  end
  content
end

The difference is, that I now have a hash called "drops" with properties
for all my partials to render in there. For each partial I create a
temporary hash called "drop" that I apply to "drops" in the end.

Then I loop over each drop and replace the {some_partial foo:bar} stuff
with a partial (like you wanted: after the StringScanner has finished).

As you can see, I'm doing a drops.inspect, and it outputs this:

{1=>{:to=>42, :partial=>"contact_form", :arguments=>{:hello=>"you",
:bar=>"foos", :foo=>"bars"}, :from=>0}, 2=>{:to=>88,
:partial=>"contact_form", :arguments=>{:hi=>"you", :its=>"a_good_day"},
:from=>43}}

It looks right but when I uncomment one of the last lines
(content[drop[:from], drop[:to]] = "...) I get this error:

Symbol as array index

But I'm not using an array!.. Am I missing something?
Frederick C. (Guest)
on 2008-10-09 14:43
(Received via mailing list)
On 9 Oct 2008, at 11:16, David T. wrote:

>>> Unfortunately that is not true. When I comment out the
>> output arguments.inspect)
>    drop[:arguments] = {}
>    drops[i] = drop
> for all my partials to render in there. For each partial I create a
> :bar=>"foos", :foo=>"bars"}, :from=>0}, 2=>{:to=>88,
> :partial
> =>"contact_form", :arguments=>{:hi=>"you", :its=>"a_good_day"},
> :from=>43}}
>
> It looks right but when I uncomment one of the last lines
> (content[drop[:from], drop[:to]] = "...) I get this error:
>
> Symbol as array index
>
> But I'm not using an array!.. Am I missing something?

Yes :-)
here drop is an array (the first element is the key, the second is the
value)
Typically one using each with a hash, one does
some_hash.each do |key, value|
...
end

lastly, why is drops a hash at all and not an array ?

Fred
David T. (Guest)
on 2008-10-09 18:14
Frederick C. wrote:

>> But I'm not using an array!.. Am I missing something?
>
> Yes :-)
> here drop is an array (the first element is the key, the second is the
> value)
> Typically one using each with a hash, one does
> some_hash.each do |key, value|
> ...
> end

All right. Fixed. :)

> lastly, why is drops a hash at all and not an array ?

I don't know, that is changed now, too. Thanks.

One last question: I'm currently replacing the {some_partial foo:bar} to
the partial like this:

content[drop[:from], drop[:to]] = render(:partial =>
"drops/#{drop[:partial]}", :locals => drop[:arguments])

But this is very buggy. The [fixnum, fixnum] method assumes that the new
thing (in my case, a partial) is of the same length as the {some_partial
foo:bar}. That means the partial is "swallowing" up the next partial if
it's too long. If my partial looks like this:

This is a test drop. Please don't edit or remove it.

and I do {some_partial foo:bar} twice I get this output:

This is a test drop. Please don’t edit or remove it.
don’t edit or remove it.

In this case the partial is longer than the {some_partial} and some of
the second instance of the partial disappears. How can I force Ruby to
replace the characters between to positions with a longer string?
Frederick C. (Guest)
on 2008-10-09 18:25
(Received via mailing list)
On 9 Oct 2008, at 15:14, David T. wrote:
>
>
> In this case the partial is longer than the {some_partial} and some of
> the second instance of the partial disappears. How can I force Ruby to
> replace the characters between to positions with a longer string?

You could fiddle with insert or something like that. There is another
way of doing it:
Say you've determine the two sections to replace are between positions
20 and 40 and then between 100 and 120
you would do

result = ""
result << original_string[0, 20]
result << render of first section
result << original_string[40, 60]
result << render of last section
result << original_string[120, original_string.length - 120

Fred
David T. (Guest)
on 2008-10-09 18:52
Frederick C. wrote:

>> In this case the partial is longer than the {some_partial} and some of
>> the second instance of the partial disappears. How can I force Ruby to
>> replace the characters between to positions with a longer string?
>
> You could fiddle with insert or something like that. There is another
> way of doing it:
> Say you've determine the two sections to replace are between positions
> 20 and 40 and then between 100 and 120
> you would do
>
> result = ""
> result << original_string[0, 20]
> result << render of first section
> result << original_string[40, 60]
> result << render of last section
> result << original_string[120, original_string.length - 120

That's seems a little complicated (and I don't see the flexibility of
it, either) but now I'm using the String#insert and at the end I do a
String#gsub to remove the {some_partial foo:bar}'s.

So my final method looks like this:

==========
def dropify(content)
  s = StringScanner.new(content)
  drops = Array.new
  while s.scan_until(/\{/)
    drop = {}
    drop[:from] = s.pointer - 1
    drop[:arguments] = {}
    drop[:partial] = s.scan(/\w+/)
    s.skip /\s+/
    while argument = s.scan(/\w+:\w+/)
      name, value = argument.split(/:/)
      drop[:arguments][name.to_sym] = value
      s.skip /\s+/
    end
    s.skip_until /\}/
    drops << drop
  end
  drops.reverse!
  drops.each do |drop|
    content.insert(drop[:from], render(:partial =>
"drops/#{drop[:partial]}", :locals => drop[:arguments]))
  end
  content.gsub!(/\{\w+.*\}/, "")
  content
end
==========

Thanks for your help! I hope some of you can use this method as well.
Frederick C. (Guest)
on 2008-10-09 19:23
(Received via mailing list)
On 9 Oct 2008, at 15:52, David T. wrote:

>> way of doing it:
>> result << original_string[120, original_string.length - 120
>
> That's seems a little complicated (and I don't see the flexibility of
> it, either) but now I'm using the String#insert and at the end I do a
> String#gsub to remove the {some_partial foo:bar}'s.
>

It avoids the need to maintain the drop array (and also means that you
don't need a gsub that as is i believe will remove the wrong content
if there are more than one partial (because the * is greedy)

you could have something like

def dropify(content)
  s = StringScanner.new(content)
  output = ""
  previous_end = 0
  while s.scan_until(/\{/)
    output << content[previous_end, s.pointer - previous_end]
    partial =  s.scan(/\w+/)
    s.skip /\s+/
    arguments = {}
    while argument = s.scan(/\w+:\w+/)
      name, value = argument.split(/:/)
      arguments[name.to_sym] = value
      s.skip /\s+/
    end
    s.skip_until /\}/
    previous_end = s.pointer
  end
  output << content[s.pointer, content.length - s.pointer]
end

which is marginally simpler (the above could contain off by 1 errors
in the various offsets).

Fred
David T. (Guest)
on 2008-10-15 11:00
Frederick C. wrote:

>> That's seems a little complicated (and I don't see the flexibility of
>> it, either) but now I'm using the String#insert and at the end I do a
>> String#gsub to remove the {some_partial foo:bar}'s.
>>
>
> It avoids the need to maintain the drop array (and also means that you
> don't need a gsub that as is i believe will remove the wrong content
> if there are more than one partial (because the * is greedy)

I admit the gsub was an emergency solution, and yes, there are a risk
that it will strip the wrong content.

> you could have something like
>
> def dropify(content)
>   s = StringScanner.new(content)
>   output = ""
>   previous_end = 0
>   while s.scan_until(/\{/)
>     output << content[previous_end, s.pointer - previous_end]
>     partial =  s.scan(/\w+/)
>     s.skip /\s+/
>     arguments = {}
>     while argument = s.scan(/\w+:\w+/)
>       name, value = argument.split(/:/)
>       arguments[name.to_sym] = value
>       s.skip /\s+/
>     end
>     s.skip_until /\}/
>     previous_end = s.pointer
>   end
>   output << content[s.pointer, content.length - s.pointer]
> end

I've been taking a look at your code and it looks interesting. But the
only thing I can't figure out is where (and how) you'ld render the
partial in the code above.

I've been testing your method and something is wrong. E.g. if you have
this content:

----------
{test}

Lorem ipsum dolor sit amet.

{test}

Lorem ipsum dolor sit amet.
----------

you'll get absolutely nothing back. I tried changing the 6th line to
this:

output << content[previous_end, s.pointer]

Still wrong:

----------
{

Lorem ipsum dolor sit amet.

Lorem ipsum dolor sit amet.

{test}

Lorem ipsum dolor sit amet.
----------

Did your method work for you, or what?

> which is marginally simpler (the above could contain off by 1 errors
> in the various offsets).

What is "off by 1 errors"?
Frederick C. (Guest)
on 2008-10-15 11:53
(Received via mailing list)
On Oct 15, 8:00 am, David T. <removed_email_address@domain.invalid>
wrote:
> Frederick C. wrote:
>
> Did your method work for you, or what?

yeah i left out that bit by accident. you should do that (ie output <<
render(...)) after I set previous_end at the end of the loop
> > which is marginally simpler (the above could contain off by 1 errors
> > in the various offsets).
>
> What is "off by 1 errors"?

some of the offsets might be off by 1 (eg someplaces where it says
pointer it might need to be s.pointer-1 etc..). More generally I just
bashed that out in Mail so I expect that   there's the odd mistake
like that in there.

Fred
David T. (Guest)
on 2008-10-15 13:11
Frederick C. wrote:

>> Did your method work for you, or what?
>
> yeah i left out that bit by accident. you should do that (ie output <<
> render(...)) after I set previous_end at the end of the loop

Okay, thanks!

>> What is "off by 1 errors"?
>
> some of the offsets might be off by 1 (eg someplaces where it says
> pointer it might need to be s.pointer-1 etc..). More generally I just
> bashed that out in Mail so I expect that   there's the odd mistake
> like that in there.

All right, with my new knowledge I've changed your method a little bit
and it works perfectly well.

So this is the new final version of the method:

==========
def dropify(content)
  s = StringScanner.new(content)
  output = ""
  previous_end = 0
  while s.scan_until(/\{/)
    output << content[previous_end, s.pointer - previous_end - 1]
    partial =  s.scan(/\w+/)
    s.skip /\s+/
    arguments = {}
    while argument = s.scan(/\w+:\w+/)
      name, value = argument.split(/:/)
      arguments[name.to_sym] = value
      s.skip /\s+/
    end
    s.skip_until /\}/
    previous_end = s.pointer
    output << render(:partial => "drops/#{partial}", :locals =>
arguments)
  end
  output << content[s.pointer, content.length - s.pointer]
end
==========

Notice that in the 6th line i subtract s.pointer-previous_end by 1 and
that's what made it work.

Thanks for your help, Fred!
David T. (Guest)
on 2008-10-15 14:01
David T. wrote:

> ==========
> def dropify(content)
>   s = StringScanner.new(content)
>   output = ""
>   previous_end = 0
>   while s.scan_until(/\{/)
>     output << content[previous_end, s.pointer - previous_end - 1]
>     partial =  s.scan(/\w+/)
>     s.skip /\s+/
>     arguments = {}
>     while argument = s.scan(/\w+:\w+/)
>       name, value = argument.split(/:/)
>       arguments[name.to_sym] = value
>       s.skip /\s+/
>     end
>     s.skip_until /\}/
>     previous_end = s.pointer
>     output << render(:partial => "drops/#{partial}", :locals =>
> arguments)
>   end
>   output << content[s.pointer, content.length - s.pointer]
> end
> ==========

Okay, I actually have a concern about this method. Right now it only
accepts this kind of syntax:

{test foo:foo_bar}

Since spaces are not covered by \w+ this is not accepted:

{test foo:foo bar}

So I tried changing \w+ to .+ but of course that is never going to work.
E.g. if I try to pass multiple arguments to the partial like this:

{test foo:foo bar bar:bar foo}

then only one argument will only be passed (foo) and it will have this
value: "foo bar bar" because the .+ includes that.

But what about putting quotation marks around it?

".+"

Still the same problem. Now foo will just have this value: "“foo bar”
bar". So, here is my question: How can I make this method accept
multiple arguments without being limited to that the values have to
match "\w+"?
Scott N. (Guest)
on 2008-10-15 22:26
(Received via mailing list)
how about doing something like this logic

scan your content looking for "{"
store everything until your next "}" into a string variable # now
string = "test foo:foo bar bar:bar foo"
split your string by spaces into an array # now array = ["test",
"foo:foo", "bar", "bar:bar", "foo"]

loop through your array
new_array = []
index = 0
array.each do |a|
    if a does not contain ":"
        index = index + 1
        partial = a
        args = {}
        new_array[index] = [partial, args]
    else
        args = new_array[index][2] # should be a hash
        name, value = a.split(/:/)
        args[name.to_sym] = value # add new args to hash
        new_array[index][2] = args # store hash back into the array
    end
end

# now new_array = [["foo", {foo => "foo"}], ["bar", {bar => "bar"}],
["foo", {}]]
loop through this and render partials
new_array.each do |a|
    partial = a[1]
    arguments = a[2]
    output << render(:partial => "drops/#{partial}", :locals =>
arguments)
end

i am sure there are errors and bugs but i think the basic logic should
work. it will probably turn out to be fairly clean and readable if
refactored into a few methods.


On Oct 15, 6:01 am, David T. <removed_email_address@domain.invalid>
David T. (Guest)
on 2008-10-16 11:36
Scott Nj wrote:

> how about doing something like this logic

It's good to hear your oppinion on it, but I actually like Fred's
approach on it a little better. It is clean and fairly readable.

> i am sure there are errors and bugs but i think the basic logic should
> work. it will probably turn out to be fairly clean and readable if
> refactored into a few methods.

The thing is that Fred helped me putting together a method and he's
spent a lot of time fixing all the bugs I found. Why would I completely
remake the method just to find a bunch of new bugs?


Okay, I think I should describe my tricky problem a little further. This
is it: The method does a scan_until(/\{/) which means it stops when
meeting a curly brace and starts looking for something matching \w+
immediately after the curly brace. That is the partial to render.

The string looks like this: {test_partial foo:"bar foo" bar:"foo bar"}

Fine. Then it starts looking for arguments immediately after the partial
and a space. My problem is that the method only supports values that
matches \w+. I would like values to be able to match .* or .+.

Now, my problem is that when I change the method so that the value
should match .+ it doesn't only take the characters between the first
and the second quotation sign (name: "foo", value: "bar foo"), it
actually goes on at takes the next quotation sign too (name: "foo",
value: "bar foo" bar:"). So that we actually get a value that contains
the right value and then the name of the NEXT argument afterwards.

How can I solve this? If you need further details, just ask.
Frederick C. (Guest)
on 2008-10-16 14:06
(Received via mailing list)
On 16 Oct 2008, at 08:36, David T. wrote:

> partial
> and a space. My problem is that the method only supports values that
> matches \w+. I would like values to be able to match .* or .+.
>
Greedy things like .* are almost always dangerous as they make you
consume the entire string. typically you'd want to scan or scan_until
the first occurence of something

the following modification to the previous solution does that:

def dropify(content)
  s = StringScanner.new(content)
  output = ""
  previous_end = 0
  while s.scan_until(/\{/)
    output << content[previous_end, s.pointer - previous_end - 1]
    partial =  s.scan(/\w+/)
    s.skip /\s+/
    arguments = {}
    while label= s.scan(/(\w+):"/)
      name=s[1]
      value = s.scan /[^"]+/
      arguments[name.to_sym] = value
      s.skip /"\s+/
    end
    s.skip_until /\}/
    previous_end = s.pointer
    puts arguments.inspect
    end
end

Having scanned \w+:" (ie a label and the opening quote mark) we
consume all non " characters and says those are the arguments. then we
skip over the closing " and any whitespace

dropify('{test_partial foo:"bar foo" bar:"foo bar"}')

outputs

{:foo=>"bar foo", :bar=>"foo bar"}

Fred
David T. (Guest)
on 2008-10-16 14:18
Frederick C. wrote:
> On 16 Oct 2008, at 08:36, David T. wrote:
>
>> partial
>> and a space. My problem is that the method only supports values that
>> matches \w+. I would like values to be able to match .* or .+.
>>
> Greedy things like .* are almost always dangerous as they make you
> consume the entire string. typically you'd want to scan or scan_until
> the first occurence of something

Yes.

> the following modification to the previous solution does that:
>
> def dropify(content)
>   s = StringScanner.new(content)
>   output = ""
>   previous_end = 0
>   while s.scan_until(/\{/)
>     output << content[previous_end, s.pointer - previous_end - 1]
>     partial =  s.scan(/\w+/)
>     s.skip /\s+/
>     arguments = {}
>     while label= s.scan(/(\w+):"/)
>       name=s[1]
>       value = s.scan /[^"]+/
>       arguments[name.to_sym] = value
>       s.skip /"\s+/
>     end
>     s.skip_until /\}/
>     previous_end = s.pointer
>     puts arguments.inspect
>     end
> end

This works perfectly. I modified it a little bit for my needs. This
method supports multiple arguments with values containing any kind of
characters:

def dropify(content)
  s = StringScanner.new(content)
  output = ""
  previous_end = 0
  while s.scan_until(/\{/)
    output << content[previous_end, s.pointer - previous_end - 1]
    partial =  s.scan(/\w+/)
    s.skip /\s+/
    arguments = {}
    while label= s.scan(/(\w+):"/)
      name = s[1]
      value = s.scan /[^"]+/
      arguments[name.to_sym] = value
      s.skip /"\s+/
    end
    s.skip_until /\}/
    previous_end = s.pointer
    output << render(:partial => "drops/#{partial}", :locals =>
arguments)
  end
  output << content[s.pointer, content.length - s.pointer]
end

> Having scanned \w+:" (ie a label and the opening quote mark) we
> consume all non " characters and says those are the arguments. then we
> skip over the closing " and any whitespace
>
> dropify('{test_partial foo:"bar foo" bar:"foo bar"}')
>
> outputs
>
> {:foo=>"bar foo", :bar=>"foo bar"}

Great! Thanks again for your help, Fred.

David T.
This topic is locked and can not be replied to.