ruby-changes:50413
From: k0kubun <ko1@a...>
Date: Thu, 22 Feb 2018 22:28:33 +0900 (JST)
Subject: [ruby-changes:50413] k0kubun:r62529 (trunk): erb.rb: deprecate safe_level of ERB.new
k0kubun 2018-02-22 22:28:25 +0900 (Thu, 22 Feb 2018) New Revision: 62529 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=62529 Log: erb.rb: deprecate safe_level of ERB.new Also, as it's in the middle of the list of 4 arguments, 3rd and 4th arguments (trim_mode, eoutvar) are changed to keyword arguments. Old ways to specify arguments are deprecated and warned now. bin/erb: deprecate -S option. We'll remove all of deprecated ones at Ruby 2.7+. enc/make_encmake.rb: stopped using deprecated interface ext/etc/mkconstants.rb: ditto ext/socket/mkconstants.rb: ditto sample/ripper/ruby2html.rb: ditto spec/ruby/library/erb/defmethod/def_erb_method_spec.rb: ditto spec/ruby/library/erb/new_spec.rb: ditto test/erb/test_erb.rb: ditto test/erb/test_erb_command.rb: ditto tool/generic_erb.rb: ditto tool/ruby_vm/helpers/dumper.rb: ditto tool/transcode-tblgen.rb: ditto lib/rdoc/erbio.rb: ditto lib/rdoc/generator/darkfish.rb: ditto [Feature #14256] Modified files: trunk/NEWS trunk/bin/erb trunk/enc/make_encmake.rb trunk/ext/etc/mkconstants.rb trunk/ext/socket/mkconstants.rb trunk/lib/erb.rb trunk/lib/rdoc/erbio.rb trunk/lib/rdoc/generator/darkfish.rb trunk/sample/ripper/ruby2html.rb trunk/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb trunk/spec/ruby/library/erb/new_spec.rb trunk/test/erb/test_erb.rb trunk/test/erb/test_erb_command.rb trunk/tool/generic_erb.rb trunk/tool/ruby_vm/helpers/dumper.rb trunk/tool/transcode-tblgen.rb Index: lib/erb.rb =================================================================== --- lib/erb.rb (revision 62528) +++ lib/erb.rb (revision 62529) @@ -115,7 +115,7 @@ require "cgi/util" https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L115 # James Edward Gray II # }.gsub(/^ /, '') # -# message = ERB.new(template, 0, "%<>") +# message = ERB.new(template, trim_mode: "%<>") # # # Set up template data. # to = "Community Spokesman <spokesman@ruby_community.org>" @@ -263,7 +263,7 @@ class ERB https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L263 # Returns revision information for the erb.rb module. def self.version - "erb.rb [2.1.0 #{ERB::Revision.split[1]}]" + "erb.rb [2.2.0 #{ERB::Revision.split[1]}]" end end @@ -777,11 +777,11 @@ class ERB https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L777 # def build # b = binding # # create and run templates, filling member data variables - # ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), 0, "", "@product").result b + # ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@product").result b # <%= PRODUCT[:name] %> # <%= PRODUCT[:desc] %> # END_PRODUCT - # ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), 0, "", "@price").result b + # ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@price").result b # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> # <%= PRODUCT[:desc] %> # END_PRICE @@ -802,7 +802,22 @@ class ERB https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L802 # Chicken Fried Steak -- 9.95 # A well messages pattie, breaded and fried. # - def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout') + def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') + # Complex initializer for $SAFE deprecation at Feature #14256, which should be removed at Ruby 2.7. + if safe_level != NOT_GIVEN + warn 'warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.' + else + safe_level = nil + end + if legacy_trim_mode != NOT_GIVEN + warn 'warning: Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.' + trim_mode = legacy_trim_mode + end + if legacy_eoutvar != NOT_GIVEN + warn 'warning: Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.' + eoutvar = legacy_eoutvar + end + @safe_level = safe_level compiler = make_compiler(trim_mode) set_eoutvar(compiler, eoutvar) @@ -810,6 +825,8 @@ class ERB https://github.com/ruby/ruby/blob/trunk/lib/erb.rb#L825 @filename = nil @lineno = 0 end + NOT_GIVEN = Object.new + private_constant :NOT_GIVEN ## # Creates a new compiler for ERB. See ERB::Compiler.new for details Index: lib/rdoc/erbio.rb =================================================================== --- lib/rdoc/erbio.rb (revision 62528) +++ lib/rdoc/erbio.rb (revision 62529) @@ -21,7 +21,11 @@ class RDoc::ERBIO < ERB https://github.com/ruby/ruby/blob/trunk/lib/rdoc/erbio.rb#L21 # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize def initialize str, safe_level = nil, trim_mode = nil, eoutvar = 'io' - super + if RUBY_VERSION >= '2.6' + super(str, trim_mode: trim_mode, eoutvar: eoutvar) + else + super + end end ## Index: lib/rdoc/generator/darkfish.rb =================================================================== --- lib/rdoc/generator/darkfish.rb (revision 62528) +++ lib/rdoc/generator/darkfish.rb (revision 62529) @@ -778,7 +778,11 @@ class RDoc::Generator::Darkfish https://github.com/ruby/ruby/blob/trunk/lib/rdoc/generator/darkfish.rb#L778 erbout = "_erbout_#{file_var}" end - template = klass.new template, nil, '<>', erbout + if RUBY_VERSION >= '2.6' + template = klass.new template, trim_mode: '<>', eoutvar: erbout + else + template = klass.new template, nil, '<>', erbout + end @template_cache[file] = template template end Index: sample/ripper/ruby2html.rb =================================================================== --- sample/ripper/ruby2html.rb (revision 62528) +++ sample/ripper/ruby2html.rb (revision 62529) @@ -73,7 +73,11 @@ class ERB https://github.com/ruby/ruby/blob/trunk/sample/ripper/ruby2html.rb#L73 end def ruby2html(f, encoding, css, print_line_number) - erb = ERB.new(TEMPLATE, nil, '>') + if RUBY_VERSION >= '2.6' + erb = ERB.new(TEMPLATE, trim_mode: '>') + else + erb = ERB.new(TEMPLATE, nil, '>') + end erb.filename = __FILE__ erb.lineno = TEMPLATE_LINE erb.result(binding()) Index: ext/socket/mkconstants.rb =================================================================== --- ext/socket/mkconstants.rb (revision 62528) +++ ext/socket/mkconstants.rb (revision 62529) @@ -73,7 +73,15 @@ def each_name(pat) https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L73 } end -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") +erb_new = lambda do |src, safe, trim| + if RUBY_VERSION >= '2.6' + ERB.new(src, trim_mode: trim) + else + ERB.new(src, safe, trim) + end +end + +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") % each_const {|guard, name, default_value| #if !defined(<%=name%>) # if defined(HAVE_CONST_<%=name.upcase%>) @@ -87,7 +95,7 @@ ERB.new(<<'EOS', nil, '%').def_method(Ob https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L95 % } EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_defs_in_guard(name, default_value)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs_in_guard(name, default_value)") #if defined(<%=name%>) /* <%= COMMENTS[name] %> */ rb_define_const(rb_cSocket, <%=c_str name%>, INTEGER2NUM(<%=name%>)); @@ -96,7 +104,7 @@ ERB.new(<<'EOS', nil, '%').def_method(Ob https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L104 #endif EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") % each_const {|guard, name, default_value| % if guard #if <%=guard%> @@ -146,7 +154,7 @@ def each_names_with_len(pat, prefix_opti https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L154 } end -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_decl(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_decl(funcname, pat, prefix_optional, guard=nil)") %if guard #ifdef <%=guard%> int <%=funcname%>(const char *str, long len, int *valp); @@ -156,7 +164,7 @@ int <%=funcname%>(const char *str, long https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L164 %end EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard=nil)") int <%=funcname%>(const char *str, long len, int *valp) { @@ -177,7 +185,7 @@ int https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L185 } EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func(funcname, pat, prefix_optional, guard=nil)") %if guard #ifdef <%=guard%> <%=gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard)%> @@ -206,7 +214,7 @@ def reverse_each_name_with_prefix_option https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L214 end end -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_hash(hash_var, pat, prefix_pat)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_hash(hash_var, pat, prefix_pat)") <%=hash_var%> = st_init_numtable(); % reverse_each_name_with_prefix_optional(pat, prefix_pat) {|n,s| #ifdef <%=n%> @@ -215,7 +223,7 @@ ERB.new(<<'EOS', nil, '%').def_method(Ob https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L223 % } EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_func(func_name, hash_var)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_func(func_name, hash_var)") ID <%=func_name%>(int val) { @@ -226,7 +234,7 @@ ID https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L234 } EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_decl(func_name, hash_var)") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_decl(func_name, hash_var)") ID <%=func_name%>(int val); EOS @@ -275,7 +283,7 @@ def_intern('rsock_intern_udp_optname', https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L283 def_intern('rsock_intern_scm_optname', /\ASCM_/, "SCM_") def_intern('rsock_intern_local_optname', /\ALOCAL_/, "LOCAL_") -result = ERB.new(<<'EOS', nil, '%').result(binding) +result = erb_new.call(<<'EOS', nil, '%').result(binding) /* autogenerated file */ <%= INTERN_DEFS.map {|vardef, gen_hash, decl, func| vardef }.join("\n") %> @@ -318,7 +326,7 @@ init_constants(void) https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L326 EOS -header_result = ERB.new(<<'EOS', nil, '%').result(binding) +header_result = erb_new.call(<<'EOS', nil, '%').result(binding) /* autogenerated file */ <%= gen_const_decls %> <%= NAME_TO_INT_DEFS.map {|decl, func| decl }.join("\n") %> Index: ext/etc/mkconstants.rb =================================================================== --- ext/etc/mkconstants.rb (revision 62528) +++ ext/etc/mkconstants.rb (revision 62529) @@ -66,7 +66,15 @@ def each_name(pat) https://github.com/ruby/ruby/blob/trunk/ext/etc/mkconstants.rb#L66 } end -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") +erb_new = lambda do |src, safe, trim| + if RUBY_VERSION >= '2.6' + ERB.new(src, trim_mode: trim) + else + ERB.new(src, safe, trim) + end +end + +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") % each_const {|name, default_value| #if !defined(<%=name%>) # if defined(HAVE_CONST_<%=name.upcase%>) @@ -80,7 +88,7 @@ ERB.new(<<'EOS', nil, '%').def_method(Ob https://github.com/ruby/ruby/blob/trunk/ext/etc/mkconstants.rb#L88 % } EOS -ERB.new(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") +erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") % each_const {|name, default_value| #if defined(<%=name%>) % if comment = COMMENTS[name] @@ -91,13 +99,13 @@ ERB.new(<<'EOS', nil, '%').def_method(Ob https://github.com/ruby/ruby/blob/trunk/ext/etc/mkconstants.rb#L99 % } EOS -header_result = ERB.new(<<'EOS', nil, '%').result(binding) +header_result = erb_new.call(<<'EOS', nil, '%').result(binding) /* autogenerated file */ <%= gen_const_decls %> EOS -result = ERB.new(<<'EOS', nil, '%').result(binding) +result = erb_new.call(<<'EOS', nil, '%').result(binding) /* autogenerated file */ #ifdef HAVE_LONG_LONG Index: spec/ruby/library/erb/defmethod/def_erb_method_spec.rb =================================================================== --- spec/ruby/library/erb/defmethod/def_erb_method_spec.rb (revision 62528) +++ spec/ruby/library/erb/defmethod/def_erb_method_spec.rb (revision 62529) @@ -50,7 +50,11 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb#L50 MY_INPUT4_FOR_ERB = input class MyClass4ForErb extend ERB::DefMethod - erb = ERB.new(MY_INPUT4_FOR_ERB, nil, '<>') + if RUBY_VERSION >= '2.6' + erb = ERB.new(MY_INPUT4_FOR_ERB, trim_mode: '<>') + else + erb = ERB.new(MY_INPUT4_FOR_ERB, nil, '<>') + end def_erb_method('render()', erb) def initialize(items) @items = items Index: spec/ruby/library/erb/new_spec.rb =================================================================== --- spec/ruby/library/erb/new_spec.rb (revision 62528) +++ spec/ruby/library/erb/new_spec.rb (revision 62529) @@ -31,21 +31,33 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/new_spec.rb#L31 it "compiles eRuby script into ruby code when trim mode is 0 or not specified" do expected = "<ul>\n\n\n\n<li>1</li>\n\n\n\n<li>2</li>\n\n\n\n<li>3</li>\n\n\n</ul>\n" [0, '', nil].each do |trim_mode| - ERB.new(@eruby_str, nil, trim_mode).result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str, trim_mode: trim_mode).result.should == expected + else + ERB.new(@eruby_str, nil, trim_mode).result.should == expected + end end end it "removes '\n' when trim_mode is 1 or '>'" do expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n" [1, '>'].each do |trim_mode| - ERB.new(@eruby_str, nil, trim_mode).result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str, trim_mode: trim_mode).result.should == expected + else + ERB.new(@eruby_str, nil, trim_mode).result.should == expected + end end end it "removes spaces at beginning of line and '\n' when trim_mode is 2 or '<>'" do expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n" [2, '<>'].each do |trim_mode| - ERB.new(@eruby_str, nil, trim_mode).result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str, trim_mode: trim_mode).result.should == expected + else + ERB.new(@eruby_str, nil, trim_mode).result.should == expected + end end end @@ -61,7 +73,11 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/new_spec.rb#L73 </ul> END - ERB.new(input, nil, '-').result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(input, trim_mode: '-').result.should == expected + else + ERB.new(input, nil, '-').result.should == expected + end end @@ -75,23 +91,39 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/new_spec.rb#L91 END lambda { - ERB.new(input, nil, '-').result + if RUBY_VERSION >= '2.6' + ERB.new(input, trim_mode: '-').result + else + ERB.new(input, nil, '-').result + end }.should raise_error(SyntaxError) end it "regards lines starting with '%' as '<% ... %>' when trim_mode is '%'" do expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n\n</ul>\n%%\n" - ERB.new(@eruby_str2, nil, "%").result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str2, trim_mode: "%").result.should == expected + else + ERB.new(@eruby_str2, nil, "%").result.should == expected + end end it "regards lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%>'" do expected = "<ul>\n <li>1 <li>2 <li>3 </ul>\n%%\n" - ERB.new(@eruby_str2, nil, '%>').result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str2, trim_mode: '%>').result.should == expected + else + ERB.new(@eruby_str2, nil, '%>').result.should == expected + end end it "regard lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%<>'" do expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n</ul>\n%%\n" - ERB.new(@eruby_str2, nil, '%<>').result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(@eruby_str2, trim_mode: '%<>').result.should == expected + else + ERB.new(@eruby_str2, nil, '%<>').result.should == expected + end end @@ -106,13 +138,22 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/new_spec.rb#L138 %%% END - ERB.new(input, nil, '%-').result.should == expected + if RUBY_VERSION >= '2.6' + ERB.new(input, trim_mode: '%-').result.should == expected + else + ERB.new(input, nil, '%-').result.should == expected + end end it "changes '_erbout' variable name in the produced source" do input = @eruby_str - match_erbout = ERB.new(input, nil, nil).src - match_buf = ERB.new(input, nil, nil, 'buf').src + if RUBY_VERSION >= '2.6' + match_erbout = ERB.new(input, trim_mode: nil).src + match_buf = ERB.new(input, trim_mode: nil, eoutvar: 'buf').src + else + match_erbout = ERB.new(input, nil, nil).src + match_buf = ERB.new(input, nil, nil, 'buf').src + end match_erbout.gsub("_erbout", "buf").should == match_buf end @@ -124,7 +165,11 @@ END https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/erb/new_spec.rb#L165 <%# end %> END ERB.new(input).result.should == "\n<b></b>\n\n" - ERB.new(input, nil, '<>').result.should == "<b></b>\n" + if RUBY_VERSION >= '2.6' + ERB.new(input, trim_mode: '<>').result.should == "<b></b>\n" + else + ERB.new(input, nil, '<>').result.should == "<b></b>\n" + end end it "forget local variables defined previous one" do Index: bin/erb =================================================================== --- bin/erb (revision 62528) +++ bin/erb (revision 62529) @@ -75,6 +75,7 @@ class ERB https://github.com/ruby/ruby/blob/trunk/bin/erb#L75 when '-r' # require require ARGV.req_arg when '-S' # security level + warn 'warning: -S option of erb command is deprecated. Please do not use this.' arg = ARGV.req_arg raise "invalid safe_level #{arg.dump}" unless arg =~ /\A[0-1]\z/ safe_level = arg.to_i @@ -112,7 +113,6 @@ class ERB https://github.com/ruby/ruby/blob/trunk/bin/erb#L113 -v enable verbose mode -d set $DEBUG to true -r library load a library - -S safe_level set $SAFE (0..1) -E ex[:in] set default external/internal encodings -U set default encoding to UTF-8. -T trim_mode specify trim_mode (0..2, -) @@ -127,7 +127,12 @@ EOU https://github.com/ruby/ruby/blob/trunk/bin/erb#L127 filename = $FILENAME exit 2 unless src trim = trim_mode_opt(trim_mode, disable_percent) - erb = factory.new(src.untaint, safe_level, trim) + if safe_level.nil? + erb = factory.new(src.untaint, trim_mode: trim) + else + # [deprecated] This will be removed at Ruby 2.7. + erb = factory.new(src.untaint, safe_level, trim_mode: trim) + end erb.filename = filename if output if number Index: NEWS =================================================================== --- NEWS (revision 62528) +++ NEWS (revision 62529) @@ -69,6 +69,11 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L69 === Stdlib updates (outstanding ones only) +* ERB + + * 2nd, 3rd and 4th arguments of ERB.new are deprecated. 2nd argument (safe_level) will be dropped in the future + and some of those arguments (trim_mode, eoutvar) are changed to keyword arguments. [Feature #14256] + * Matrix * New method: Index: enc/make_encmake.rb =================================================================== --- enc/make_encmake.rb (revision 62528) +++ enc/make_encmake.rb (revision 62529) @@ -121,7 +121,11 @@ ENCS, ENC_DEPS = target_encodings https://github.com/ruby/ruby/blob/trunk/enc/make_encmake.rb#L121 ATRANS, TRANS = target_transcoders if File.exist?(depend = File.join($srcdir, "depend")) - erb = ERB.new(File.read(depend), nil, '%') + if RUBY_VERSION >= '2.6' + erb = ERB.new(File.read(depend), trim_mode: '%') + else + erb = ERB.new(File.read(depend), nil, '%') + end erb.filename = depend tmp = erb.result(binding) dep (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/