Named sprintf parameters

T

Trans

I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

"I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:

"I am %(name)s." % binding

Making use of the binding's local_variables.

The challenge, of course, is to override printf/sprintf to do this
(the binding part gets you extra ruby points ;-) Even more
challenging, augment the C code to handle it and submit it as a patch.

T.
 
A

ara howard

I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

"I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:

"I am %(name)s." % binding

Making use of the binding's local_variables.

The challenge, of course, is to override printf/sprintf to do this
(the binding part gets you extra ruby points ;-) Even more
challenging, augment the C code to handle it and submit it as a patch.

T.


class String
module M
def % *args
newstuff
super
end
end
include M
end

a @ http://codeforpeople.com/
 
T

t3ch.dude

I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

  "I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:

  "I am %(name)s." % binding

Making use of the binding's local_variables.

The challenge, of course, is to override printf/sprintf to do this
(the binding part gets you extra ruby points ;-)  Even more
challenging, augment the C code to handle it and submit it as a patch.

T.

That exact feature exists in that form in Python today...
http://docs.python.org/lib/typesseq-strings.html

-T3ch Dude
 
Y

yermej

class String
module M
def % *args
newstuff
super
end
end
include M
end

Can you please explain this? It looks to me like it would allow you to
override a method, but still call the original without using alias.
However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called. Is that a 1.9
thing or am I misunderstanding what you meant?
 
A

Adam Shelly

However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called. ...

I also couldn't get Ara's example to work, so I resorted to monkeypatching.
(Yes, I know, I am Destroying Ruby..)

----------

p "I am %s." % "string"
p "I am %s." % ["array"]
p "I am %s." % { :name => "hash" }
begin
p "I am %(name)s." % { :name => "hash" }
rescue ArgumentError => err
p "caught #{err}"
end

puts "\nMonkeyPatching!\n\n"
class String
alias :eek:ld_percent :%
def % arg
arr = (arg.kind_of? Array) ? arg : [arg]
target = self.gsub(/%\((.+?)\)/){|m|
raise ArgumentError, "named parameters need hash" unless arg.kind_of? Hash
arr << arg[$1.to_sym]
"%#{arr.size}$"
}
target.old_percent arr
end
end

p "I am %s." % "string"
p "I am %s." % ["array"]
p "I am %s." % { :name => "hash" }
p "I am %(name)s." % { :name => "hash" }

p "I am %(name)s %(with)s %(what)s"%{:what => "multiple arguments",
:with => "including", :name=> 'hash'}
p "I am %(name)s %(with)s %1$p"%{:with => "containing", :name=> 'hash'}
p "I am %(name)s." % [:name, "array"]


-Adam

P.S. It actually looks like patching sprintf wouldn't be too hard, as
long as you disallowed mixing named arguments with any other type.
 
R

Rick DeNatale

Can you please explain this? It looks to me like it would allow you to
override a method, but still call the original without using alias.
However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called. Is that a 1.9
thing or am I misunderstanding what you meant?

No you are right. Including a module in a class effectively inserts
the module above the class in the inheritance chain rather than below,
a class can override methods in a module it includes, not the other
way around. I missed Ara's point as well.

Along these lines I was scratching my head the other night when I was
reading Russ Olsen's "Design Patterns in Ruby" and he was showing an
implementation of the Decorator pattern using modules as decorators, I
missed the subtlety that he was using Object#extend to include a
module above the singleton class of a particular object rather than
including it in a 'real' class. But I don't think that technique
applies here.
 
G

Gary Wright

I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

"I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language?

I released a gem last year (http://jig.rubyforge.com) that defines
a data structure I nicknamed a Jig that enables this sort of thing
either by parsing a string to get the format or by explicit
construction:

j = Jig.new('I am ', :name)
j2 = Jig.parse('I am %{:name:}')

puts j % { :name => 'Tom'} # I am Tom
puts j2 % { :name => 'Alice'} # I am Alice

A Jig is a ordered sequence of objects and 'gaps'. Simple gaps are
identified as symbols during Jig construction but you can also define
gaps that process anything that is used to fill them:

capitalize = Jig::Gap.new:)name) { |x| x.capitalize }
j2 = Jig.new('before: ', :name, ' after: ', capitalize)
puts j2 % {:name => 'bob' } # 'before: bob after: Bob'

In that example you can see that all gaps with the same name
are plugged simultaneously. Unplugged gaps are rendered as
the empty string when Jig#to_s is called.

You can also construct a Jig or fill a gap with a proc/lambda/method
which will be evaluated at the time the Jig is rendered to a string:

time = Jig.new("The time is ") {Time.now}
puts time # The time is Thu Feb 28 18:16:07 -0500 2008
sleep 5
puts time # The time is Thu Feb 28 18:16:12 -0500 2008

The gem has classes for handling XML, XHTML, and CSS constructs.

I've made a few changes since the initialize release but haven't
gotten around to pushing it a new release to Rubyforge yet....

Gary Wright

http://jig.rubyforge.com
 
A

ara howard

Can you please explain this? It looks to me like it would allow you to
override a method, but still call the original without using alias.
However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called. Is that a 1.9
thing or am I misunderstanding what you meant?

i was just being stupid. you'd need something like this


cfp2:~ > cat a.rb
class String

Percent = instance_method '%' unless defined? Percent

def % *a, &b
a.flatten!
string =
case a.last
when Hash
expand a.pop
else
self
end
if a.empty?
string
else
Percent.bind(string).call(*a, &b)
end
end

def expand! vars = {}
loop do
changed = false
vars.each do |var, value|
var = var.to_s
var.gsub! %r/[^a-zA-Z0-9_]/, ''
[
%r/\$#{ var }\b/,
%r/\@#{ var }\b/,
%r/\$\{\s*#{ var }\s*\}/,
%r/\@\{\s*#{ var }\s*\}/,
].each do |pat|
changed = gsub! pat, "#{ value }"
end
end
break unless changed
end
self
end

def expand opts = {}
dup.expand! opts
end

end

puts( 'I am @name'.expand:)name => 'Ara') )
puts( 'I am @name' % {:name => 'Ara'} )



cfp2:~ > ruby a.rb
I am Ara
I am Ara

a @ http://codeforpeople.com/
 
N

Nobuyoshi Nakada

Hi,

At Fri, 29 Feb 2008 01:57:23 +0900,
Trans wrote in [ruby-talk:292860]:
I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

Yes, once I had posted the patch for it, and was rejected.
"I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:

"I am %(name)s." % binding

Making use of the binding's local_variables.

Though I don't feel it attractive, I believe named parameter is
an important feature for I18N, and IIRC, ruby-gettext has it.
 
G

Gregory Seidman

Hi,

At Fri, 29 Feb 2008 01:57:23 +0900,
Trans wrote in [ruby-talk:292860]:
I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

Yes, once I had posted the patch for it, and was rejected.
"I am %(name)s." % { :name => "Tom" ]

Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:

"I am %(name)s." % binding

Making use of the binding's local_variables.

Though I don't feel it attractive, I believe named parameter is
an important feature for I18N, and IIRC, ruby-gettext has it.

You may find the recently announced Jig library of interest. I was sitting
next to Gary during something at RubyConf, maybe RejectConf, and he gave me
a demo. Pretty cool. See http://www.ruby-forum.com/topic/144356
Nobu Nakada
--Greg
 
T

Trans

Hi,

At Fri, 29 Feb 2008 01:57:23 +0900,
Trans wrote in [ruby-talk:292860]:
I have a question and perhaps a bit of challenge for those with mad
parse skills: Has anyone ever considered named parameters for sprintf/
printf? It would be quite useful (to me at least) to be able to do:

Yes, once I had posted the patch for it, and was rejected.
:(
"I am %(name)s." % { :name => "Tom" ]
Has anyone worked on something like this before? Is there anything
equivalent in the Perl world or other language? I realize we can use
numerals to identify the substitutions, but I feel the labels are much
more readable. Moreover, ultimately it would be interesting to see:
"I am %(name)s." % binding
Making use of the binding's local_variables.

Though I don't feel it attractive, I believe named parameter is
an important feature for I18N, and IIRC, ruby-gettext has it.

Really? It seems like a nice way to apply parameter to templates to
me. Rather then, say,

xml = %{
<customer id="#{params[:id]}">
<name>#{params[:name]}</name>
</customer>
}

One could do:

xml = %{
<customer id="%(id)u">
<name>%(name)s</name>
</customer>
} % params

A little cleaner --and provides a nice means of reusable
interpolation.

I'll have to look at ruby-gettext.

Thanks,
T.
 
T

Trans

However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called.
...

I also couldn't get Ara's example to work, so I resorted to monkeypatching.
(Yes, I know, I am Destroying Ruby..)

----------

p "I am %s." % "string"
p "I am %s." % ["array"]
p "I am %s." % { :name => "hash" }
begin
p "I am %(name)s." % { :name => "hash" }
rescue ArgumentError => err
p "caught #{err}"
end

puts "\nMonkeyPatching!\n\n"
class String
alias :eek:ld_percent :%
def % arg
arr = (arg.kind_of? Array) ? arg : [arg]
target = self.gsub(/%\((.+?)\)/){|m|
raise ArgumentError, "named parameters need hash" unless arg.kind_of? Hash
arr << arg[$1.to_sym]
"%#{arr.size}$"
}
target.old_percent arr
end
end

p "I am %s." % "string"
p "I am %s." % ["array"]
p "I am %s." % { :name => "hash" }
p "I am %(name)s." % { :name => "hash" }

p "I am %(name)s %(with)s %(what)s"%{:what => "multiple arguments",
:with => "including", :name=> 'hash'}
p "I am %(name)s %(with)s %1$p"%{:with => "containing", :name=> 'hash'}
p "I am %(name)s." % [:name, "array"]

-Adam

Looks like you get the prize ;)

I would like to add this to Facets, though under a slightly different
naming of course. Not sure what though #printfv #sprintfv ?

Thanks!
T.
 
T

Trans

Can you please explain this? It looks to me like it would allow you to
override a method, but still call the original without using alias.
However, when I try anything like that, it only calls the original
method. The method defined inside of M isn't called. Is that a 1.9
thing or am I misunderstanding what you meant?

i was just being stupid. you'd need something like this

cfp2:~ > cat a.rb
class String

Percent = instance_method '%' unless defined? Percent

def % *a, &b
a.flatten!
string =
case a.last
when Hash
expand a.pop
else
self
end
if a.empty?
string
else
Percent.bind(string).call(*a, &b)
end
end

def expand! vars = {}
loop do
changed = false
vars.each do |var, value|
var = var.to_s
var.gsub! %r/[^a-zA-Z0-9_]/, ''
[
%r/\$#{ var }\b/,
%r/\@#{ var }\b/,
%r/\$\{\s*#{ var }\s*\}/,
%r/\@\{\s*#{ var }\s*\}/,
].each do |pat|
changed = gsub! pat, "#{ value }"
end
end
break unless changed
end
self
end

def expand opts = {}
dup.expand! opts
end

end

puts( 'I am @name'.expand:)name => 'Ara') )
puts( 'I am @name' % {:name => 'Ara'} )

cfp2:~ > ruby a.rb
I am Ara
I am Ara

Hmmm... this looks more like an answer to the interpolation question
asked in a earlier thread. Is this a full-fledge implementation? Would
it be a better solution then:

def String.interpolate(&str)
eval "%{#{str.call}}", str.binding
end

T.
 
V

Von Fugal

Adam Shelly wrote:
puts "\nMonkeyPatching!\n\n"
class String
alias :eek:ld_percent :%
def % arg
arr = (arg.kind_of? Array) ? arg : [arg]
target = self.gsub(/%\((.+?)\)/){|m|
raise ArgumentError, "named parameters need hash" unless
arg.kind_of? Hash
arr << arg[$1.to_sym]
"%#{arr.size}$"
}
target.old_percent arr
end
end
<snip/>

Don't forget the escaped %, and also, I propose the format %foo:s
instead of %(foo)s. And heres some more code to chew on, based off of
Adam's:

class String
alias old_percent %
def % arg
names = []
target = gsub /(^|[^%])%(\w+):/ do
name, space = $2, $1 # don't forget to replace whatever wasn't %
names << name unless names.include? name
"#{space}%#{names.rindex(name) + 1}$"
end
args = case arg
when Hash: names.map {|n| arg[n] or arg[n.to_sym]} #keys are often
symbols
when Array: arg
else [arg]
end
old_percent args
end
end
 
V

Von Fugal

I forgot one important point... Don't make me use a hash if I don't want
to! (hence mapping the hash to array or just using an array if no hash)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,999
Messages
2,570,246
Members
46,839
Latest member
MartinaBur

Latest Threads

Top