Class Irc::Bot::Plugins::PluginManagerClass
In: lib/rbot/plugins.rb
Parent: Object
BasicUserMessage JoinMessage NamesMessage WhoisMessage ModeChangeMessage KickMessage MotdMessage QuitMessage BanlistMessage UserMessage NoSuchTargetMessage TopicMessage NickMessage WelcomeMessage UnknownMessage InviteMessage PartMessage NetmaskList UserList ArrayOf ChannelList Netmask User\n[lib/rbot/botuser.rb\nlib/rbot/irc.rb] Channel Singleton RfcCasemap StrictRfcCasemap AsciiCasemap Casemap PrivMessage NoticeMessage TokyoCabinet::BDB CIBDB Btree CIBtree Socket MessageQueue QueueRing Client DBHash\n[lib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] DBTree\n[lib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] Server NetmaskDb Bot\n[lib/rbot/botuser.rb\nlib/rbot/config.rb\nlib/rbot/ircbot.rb\nlib/rbot/language.rb\nlib/rbot/message.rb\nlib/rbot/messagemapper.rb\nlib/rbot/plugins.rb\nlib/rbot/rbotconfig.rb\nlib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] lib/rbot/ircsocket.rb lib/rbot/rfc2812.rb lib/rbot/registry/tc.rb lib/rbot/irc.rb lib/rbot/maskdb.rb lib/rbot/message.rb lib/rbot/messagemapper.rb lib/rbot/botuser.rb lib/rbot/registry/tc.rb (null) BotConfig PKGConfig ServerOrCasemap Irc dot/m_35_0.png

Singleton to manage multiple plugins and delegate messages to them for handling

Methods

Included Modules

Singleton

Constants

DEFAULT_DELEGATE_PATTERNS = %r{^(?: connect|names|nick| listen|ctcp_listen|privmsg|unreplied| kick|join|part|quit| save|cleanup|flush_registry| set_.*|event_.* )$}x   This is the list of patterns commonly delegated to plugins. A fast delegation lookup is enabled for them.

Attributes

bot  [R] 
botmodules  [R] 
maps  [R] 

Public Class methods

[Source]

     # File lib/rbot/plugins.rb, line 422
422:     def initialize
423:       @botmodules = {
424:         :CoreBotModule => [],
425:         :Plugin => []
426:       }
427: 
428:       @names_hash = Hash.new
429:       @commandmappers = Hash.new
430:       @maps = Hash.new
431: 
432:       # modules will be sorted on first delegate call
433:       @sorted_modules = nil
434: 
435:       @delegate_list = Hash.new { |h, k|
436:         h[k] = Array.new
437:       }
438: 
439:       @core_module_dirs = []
440:       @plugin_dirs = []
441: 
442:       @failed = Array.new
443:       @ignored = Array.new
444: 
445:       bot_associate(nil)
446:     end

Public Instance methods

Returns the botmodule with the given name

[Source]

     # File lib/rbot/plugins.rb, line 479
479:     def [](name)
480:       @names_hash[name.to_sym]
481:     end

[Source]

     # File lib/rbot/plugins.rb, line 508
508:     def add_botmodule(botmodule)
509:       raise TypeError, "Argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
510:       kl = botmodule.botmodule_class
511:       if @names_hash.has_key?(botmodule.to_sym)
512:         case self[botmodule].botmodule_class
513:         when kl
514:           raise "#{kl} #{botmodule} already registered!"
515:         else
516:           raise "#{self[botmodule].botmodule_class} #{botmodule} already registered, cannot re-register as #{kl}"
517:         end
518:       end
519:       @botmodules[kl] << botmodule
520:       @names_hash[botmodule.to_sym] = botmodule
521:       mark_priorities_dirty
522:     end

add one or more directories to the list of directories to load core modules from

[Source]

     # File lib/rbot/plugins.rb, line 614
614:     def add_core_module_dir(*dirlist)
615:       @core_module_dirs += dirlist
616:       debug "Core module loading paths: #{@core_module_dirs.join(', ')}"
617:     end

add one or more directories to the list of directories to load plugins from

[Source]

     # File lib/rbot/plugins.rb, line 621
621:     def add_plugin_dir(*dirlist)
622:       @plugin_dirs += dirlist
623:       debug "Plugin loading paths: #{@plugin_dirs.join(', ')}"
624:     end

Associate with bot bot

[Source]

     # File lib/rbot/plugins.rb, line 473
473:     def bot_associate(bot)
474:       reset_botmodule_lists
475:       @bot = bot
476:     end

call the cleanup method for each active plugin

[Source]

     # File lib/rbot/plugins.rb, line 716
716:     def cleanup
717:       delegate 'cleanup'
718:       reset_botmodule_lists
719:     end

[Source]

     # File lib/rbot/plugins.rb, line 626
626:     def clear_botmodule_dirs
627:       @core_module_dirs.clear
628:       @plugin_dirs.clear
629:       debug "Core module and plugin loading paths cleared"
630:     end

Returns a hash of the registered message prefixes and associated plugins

[Source]

     # File lib/rbot/plugins.rb, line 536
536:     def commands
537:       @commandmappers
538:     end

[Source]

     # File lib/rbot/plugins.rb, line 802
802:     def core_length
803:       core_modules.length
804:     end

Returns an array of the loaded plugins

[Source]

     # File lib/rbot/plugins.rb, line 525
525:     def core_modules
526:       @botmodules[:CoreBotModule]
527:     end

see if each plugin handles method, and if so, call it, passing m as a parameter (if present). BotModules are called in order of priority from lowest to highest.

If the passed m is a BasicUserMessage and is marked as ignored?, it will only be delegated to plugins with negative priority. Conversely, if it‘s a fake message (see BotModule#fake_message), it will only be delegated to plugins with positive priority.

Note that m can also be an exploded Array, but in this case the last element of it cannot be a Hash, or it will be interpreted as the options Hash for delegate itself. The last element can be a subclass of a Hash, though. To be on the safe side, you can add an empty Hash as last parameter for delegate when calling it with an exploded Array:

  @bot.plugins.delegate(method, *(args.push Hash.new))

Currently supported options are the following:

:above :if specified, the delegation will only consider plugins with a priority higher than the specified value
:below :if specified, the delegation will only consider plugins with a priority lower than the specified value

[Source]

     # File lib/rbot/plugins.rb, line 905
905:     def delegate(method, *args)
906:       # if the priorities order of the delegate list is dirty,
907:       # meaning some modules have been added or priorities have been
908:       # changed, then the delegate list will need to be sorted before
909:       # delegation.  This should always be true for the first delegation.
910:       sort_modules unless @sorted_modules
911: 
912:       opts = {}
913:       opts.merge(args.pop) if args.last.class == Hash
914: 
915:       m = args.first
916:       if BasicUserMessage === m
917:         # ignored messages should not be delegated
918:         # to plugins with positive priority
919:         opts[:below] ||= 0 if m.ignored?
920:         # fake messages should not be delegated
921:         # to plugins with negative priority
922:         opts[:above] ||= 0 if m.recurse_depth > 0
923:       end
924: 
925:       above = opts[:above]
926:       below = opts[:below]
927: 
928:       # debug "Delegating #{method.inspect}"
929:       ret = Array.new
930:       if method.match(DEFAULT_DELEGATE_PATTERNS)
931:         debug "fast-delegating #{method}"
932:         m = method.to_sym
933:         debug "no-one to delegate to" unless @delegate_list.has_key?(m)
934:         return [] unless @delegate_list.has_key?(m)
935:         @delegate_list[m].each { |p|
936:           begin
937:             prio = p.priority
938:             unless (above and above >= prio) or (below and below <= prio)
939:               ret.push p.send(method, *args)
940:             end
941:           rescue Exception => err
942:             raise if err.kind_of?(SystemExit)
943:             error report_error("#{p.botmodule_class} #{p.name} #{method}() failed:", err)
944:             raise if err.kind_of?(BDB::Fatal)
945:           end
946:         }
947:       else
948:         debug "slow-delegating #{method}"
949:         @sorted_modules.each { |p|
950:           if(p.respond_to? method)
951:             begin
952:               # debug "#{p.botmodule_class} #{p.name} responds"
953:               prio = p.priority
954:               unless (above and above >= prio) or (below and below <= prio)
955:                 ret.push p.send(method, *args)
956:               end
957:             rescue Exception => err
958:               raise if err.kind_of?(SystemExit)
959:               error report_error("#{p.botmodule_class} #{p.name} #{method}() failed:", err)
960:               raise if err.kind_of?(BDB::Fatal)
961:             end
962:           end
963:         }
964:       end
965:       return ret
966:       # debug "Finished delegating #{method.inspect}"
967:     end

return help for topic (call associated plugin‘s help method)

[Source]

     # File lib/rbot/plugins.rb, line 807
807:     def help(topic="")
808:       case topic
809:       when /fail(?:ed)?\s*plugins?.*(trace(?:back)?s?)?/
810:         # debug "Failures: #{@failed.inspect}"
811:         return _("no plugins failed to load") if @failed.empty?
812:         return @failed.collect { |p|
813:           _('%{highlight}%{plugin}%{highlight} in %{dir} failed with error %{exception}: %{reason}') % {
814:               :highlight => Bold, :plugin => p[:name], :dir => p[:dir],
815:               :exception => p[:reason].class, :reason => p[:reason],
816:           } + if $1 && !p[:reason].backtrace.empty?
817:                 _('at %{backtrace}') % {:backtrace => p[:reason].backtrace.join(', ')}
818:               else
819:                 ''
820:               end
821:         }.join("\n")
822:       when /ignored?\s*plugins?/
823:         return _('no plugins were ignored') if @ignored.empty?
824: 
825:         tmp = Hash.new
826:         @ignored.each do |p|
827:           reason = p[:loaded] ? _('overruled by previous') : _(p[:reason].to_s)
828:           ((tmp[p[:dir]] ||= Hash.new)[reason] ||= Array.new).push(p[:name])
829:         end
830: 
831:         return tmp.map do |dir, reasons|
832:           # FIXME get rid of these string concatenations to make gettext easier
833:           s = reasons.map { |r, list|
834:             list.map { |_| _.sub(/\.rb$/, '') }.join(', ') + " (#{r})"
835:           }.join('; ')
836:           "in #{dir}: #{s}"
837:         end.join('; ')
838:       when /^(\S+)\s*(.*)$/
839:         key = $1
840:         params = $2
841: 
842:         # Let's see if we can match a plugin by the given name
843:         (core_modules + plugins).each { |p|
844:           next unless p.name == key
845:           begin
846:             return p.help(key, params)
847:           rescue Exception => err
848:             #rescue TimeoutError, StandardError, NameError, SyntaxError => err
849:             error report_error("#{p.botmodule_class} #{p.name} help() failed:", err)
850:           end
851:         }
852: 
853:         # Nope, let's see if it's a command, and ask for help at the corresponding botmodule
854:         k = key.to_sym
855:         if commands.has_key?(k)
856:           p = commands[k][:botmodule]
857:           begin
858:             return p.help(key, params)
859:           rescue Exception => err
860:             #rescue TimeoutError, StandardError, NameError, SyntaxError => err
861:             error report_error("#{p.botmodule_class} #{p.name} help() failed:", err)
862:           end
863:         end
864:       end
865:       return false
866:     end

return list of help topics (plugin names)

[Source]

     # File lib/rbot/plugins.rb, line 792
792:     def helptopics
793:       rv = status
794:       @failures_shown = true
795:       rv
796:     end

[Source]

     # File lib/rbot/plugins.rb, line 448
448:     def inspect
449:       ret = self.to_s[0..-2]
450:       ret << ' corebotmodules='
451:       ret << @botmodules[:CoreBotModule].map { |m|
452:         m.name
453:       }.inspect
454:       ret << ' plugins='
455:       ret << @botmodules[:Plugin].map { |m|
456:         m.name
457:       }.inspect
458:       ret << ">"
459:     end

delegate IRC messages, by delegating ‘listen’ first, and the actual method afterwards. Delegating ‘privmsg’ also delegates ctcp_listen and message as appropriate.

[Source]

      # File lib/rbot/plugins.rb, line 1009
1009:     def irc_delegate(method, m)
1010:       delegate('listen', m)
1011:       if method.to_sym == :privmsg
1012:         delegate('ctcp_listen', m) if m.ctcp
1013:         delegate('message', m)
1014:         privmsg(m) if m.address? and not m.ignored?
1015:         delegate('unreplied', m) unless m.replied
1016:       else
1017:         delegate(method, m)
1018:       end
1019:     end

[Source]

     # File lib/rbot/plugins.rb, line 798
798:     def length
799:       plugins.length
800:     end

Tells the PluginManager that the next time it delegates an event, it should sort the modules by priority

[Source]

     # File lib/rbot/plugins.rb, line 542
542:     def mark_priorities_dirty
543:       @sorted_modules = nil
544:     end

Returns an array of the loaded plugins

[Source]

     # File lib/rbot/plugins.rb, line 530
530:     def plugins
531:       @botmodules[:Plugin]
532:     end

see if we have a plugin that wants to handle this message, if so, pass it to the plugin and return true, otherwise false

[Source]

      # File lib/rbot/plugins.rb, line 971
 971:     def privmsg(m)
 972:       debug "Delegating privmsg #{m.inspect} with pluginkey #{m.plugin.inspect}"
 973:       return unless m.plugin
 974:       k = m.plugin.to_sym
 975:       if commands.has_key?(k)
 976:         p = commands[k][:botmodule]
 977:         a = commands[k][:auth]
 978:         # We check here for things that don't check themselves
 979:         # (e.g. mapped things)
 980:         debug "Checking auth ..."
 981:         if a.nil? || @bot.auth.allow?(a, m.source, m.replyto)
 982:           debug "Checking response ..."
 983:           if p.respond_to?("privmsg")
 984:             begin
 985:               debug "#{p.botmodule_class} #{p.name} responds"
 986:               p.privmsg(m)
 987:             rescue Exception => err
 988:               raise if err.kind_of?(SystemExit)
 989:               error report_error("#{p.botmodule_class} #{p.name} privmsg() failed:", err)
 990:               raise if err.kind_of?(BDB::Fatal)
 991:             end
 992:             debug "Successfully delegated #{m.inspect}"
 993:             return true
 994:           else
 995:             debug "#{p.botmodule_class} #{p.name} is registered, but it doesn't respond to privmsg()"
 996:           end
 997:         else
 998:           debug "#{p.botmodule_class} #{p.name} is registered, but #{m.source} isn't allowed to call #{m.plugin.inspect} on #{m.replyto}"
 999:         end
1000:       else
1001:         debug "Command #{k} isn't handled"
1002:       end
1003:       return false
1004:     end

Registers botmodule botmodule with command cmd and command path auth_path

[Source]

     # File lib/rbot/plugins.rb, line 490
490:     def register(botmodule, cmd, auth_path)
491:       raise TypeError, "First argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
492:       @commandmappers[cmd.to_sym] = {:botmodule => botmodule, :auth => auth_path}
493:     end

Registers botmodule botmodule with map map. This adds the map to the maps hash which has three keys:

botmodule:the associated botmodule
auth:an array of auth keys checked by the map; the first is the full_auth_path of the map
map:the actual MessageTemplate object

[Source]

     # File lib/rbot/plugins.rb, line 503
503:     def register_map(botmodule, map)
504:       raise TypeError, "First argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
505:       @maps[map.template] = { :botmodule => botmodule, :auth => [map.options[:full_auth_path]], :map => map }
506:     end

Makes a string of error err by adding text str

[Source]

     # File lib/rbot/plugins.rb, line 547
547:     def report_error(str, err)
548:       ([str, err.inspect] + err.backtrace).join("\n")
549:     end

drop all plugins and rescan plugins on disk calls save and cleanup for each plugin before dropping them

[Source]

     # File lib/rbot/plugins.rb, line 723
723:     def rescan
724:       save
725:       cleanup
726:       scan
727:     end

Reset lists of botmodules

[Source]

     # File lib/rbot/plugins.rb, line 462
462:     def reset_botmodule_lists
463:       @botmodules[:CoreBotModule].clear
464:       @botmodules[:Plugin].clear
465:       @names_hash.clear
466:       @commandmappers.clear
467:       @maps.clear
468:       @failures_shown = false
469:       mark_priorities_dirty
470:     end

call the save method for each active plugin

[Source]

     # File lib/rbot/plugins.rb, line 710
710:     def save
711:       delegate 'flush_registry'
712:       delegate 'save'
713:     end

load plugins from pre-assigned list of directories

[Source]

     # File lib/rbot/plugins.rb, line 692
692:     def scan
693:       @failed.clear
694:       @ignored.clear
695:       @delegate_list.clear
696: 
697:       scan_botmodules(:type => :core)
698:       scan_botmodules(:type => :plugins)
699: 
700:       debug "finished loading plugins: #{status(true)}"
701:       (core_modules + plugins).each { |p|
702:        p.methods.grep(DEFAULT_DELEGATE_PATTERNS).each { |m|
703:          @delegate_list[m.intern] << p
704:        }
705:       }
706:       mark_priorities_dirty
707:     end

[Source]

     # File lib/rbot/plugins.rb, line 632
632:     def scan_botmodules(opts={})
633:       type = opts[:type]
634:       processed = Hash.new
635: 
636:       case type
637:       when :core
638:         dirs = @core_module_dirs
639:       when :plugins
640:         dirs = @plugin_dirs
641: 
642:         @bot.config['plugins.blacklist'].each { |p|
643:           pn = p + ".rb"
644:           processed[pn.intern] = :blacklisted
645:         }
646: 
647:         whitelist = @bot.config['plugins.whitelist'].map { |p|
648:           p + ".rb"
649:         }
650:       end
651: 
652:       dirs.each do |dir|
653:         next unless FileTest.directory?(dir)
654:         d = Dir.new(dir)
655:         d.sort.each do |file|
656:           next unless file =~ /\.rb$/
657:           next if file =~ /^\./
658: 
659:           case type
660:           when :plugins
661:             if !whitelist.empty? && !whitelist.include?(file)
662:               @ignored << {:name => file, :dir => dir, :reason => "not whitelisted""not whitelisted" }
663:               next
664:             elsif processed.has_key?(file.intern)
665:               @ignored << {:name => file, :dir => dir, :reason => processed[file.intern]}
666:               next
667:             end
668: 
669:             if(file =~ /^(.+\.rb)\.disabled$/)
670:               # GB: Do we want to do this? This means that a disabled plugin in a directory
671:               #     will disable in all subsequent directories. This was probably meant
672:               #     to be used before plugins.blacklist was implemented, so I think
673:               #     we don't need this anymore
674:               processed[$1.intern] = :disabled
675:               @ignored << {:name => $1, :dir => dir, :reason => processed[$1.intern]}
676:               next
677:             end
678:           end
679: 
680:           did_it = load_botmodule_file("#{dir}/#{file}", "plugin")
681:           case did_it
682:           when Symbol
683:             processed[file.intern] = did_it
684:           when Exception
685:             @failed << { :name => file, :dir => dir, :reason => did_it }
686:           end
687:         end
688:       end
689:     end

[Source]

     # File lib/rbot/plugins.rb, line 868
868:     def sort_modules
869:       @sorted_modules = (core_modules + plugins).sort do |a, b|
870:         a.priority <=> b.priority
871:       end || []
872: 
873:       @delegate_list.each_value do |list|
874:         list.sort! {|a,b| a.priority <=> b.priority}
875:       end
876:     end

[Source]

     # File lib/rbot/plugins.rb, line 729
729:     def status(short=false)
730:       output = []
731:       if self.core_length > 0
732:         if short
733:           output << n_("%{count} core module loaded", "%{count} core modules loaded",
734:                     self.core_length) % {:count => self.core_length}
735:         else
736:           output <<  n_("%{count} core module: %{list}",
737:                      "%{count} core modules: %{list}", self.core_length) %
738:                      { :count => self.core_length,
739:                        :list => core_modules.collect{ |p| p.name}.sort.join(", ") }
740:         end
741:       else
742:         output << _("no core botmodules loaded")
743:       end
744:       # Active plugins first
745:       if(self.length > 0)
746:         if short
747:           output << n_("%{count} plugin loaded", "%{count} plugins loaded",
748:                        self.length) % {:count => self.length}
749:         else
750:           output << n_("%{count} plugin: %{list}",
751:                        "%{count} plugins: %{list}", self.length) %
752:                    { :count => self.length,
753:                      :list => plugins.collect{ |p| p.name}.sort.join(", ") }
754:         end
755:       else
756:         output << "no plugins active"
757:       end
758:       # Ignored plugins next
759:       unless @ignored.empty? or @failures_shown
760:         if short
761:           output << n_("%{highlight}%{count} plugin ignored%{highlight}",
762:                        "%{highlight}%{count} plugins ignored%{highlight}",
763:                        @ignored.length) %
764:                     { :count => @ignored.length, :highlight => Underline }
765:         else
766:           output << n_("%{highlight}%{count} plugin ignored%{highlight}: use %{bold}%{command}%{bold} to see why",
767:                        "%{highlight}%{count} plugins ignored%{highlight}: use %{bold}%{command}%{bold} to see why",
768:                        @ignored.length) %
769:                     { :count => @ignored.length, :highlight => Underline,
770:                       :bold => Bold, :command => "help ignored plugins"}
771:         end
772:       end
773:       # Failed plugins next
774:       unless @failed.empty? or @failures_shown
775:         if short
776:           output << n_("%{highlight}%{count} plugin failed to load%{highlight}",
777:                        "%{highlight}%{count} plugins failed to load%{highlight}",
778:                        @failed.length) %
779:                     { :count => @failed.length, :highlight => Reverse }
780:         else
781:           output << n_("%{highlight}%{count} plugin failed to load%{highlight}: use %{bold}%{command}%{bold} to see why",
782:                        "%{highlight}%{count} plugins failed to load%{highlight}: use %{bold}%{command}%{bold} to see why",
783:                        @failed.length) %
784:                     { :count => @failed.length, :highlight => Reverse,
785:                       :bold => Bold, :command => "help failed plugins"}
786:         end
787:       end
788:       output.join '; '
789:     end

Returns true if cmd has already been registered as a command

[Source]

     # File lib/rbot/plugins.rb, line 484
484:     def who_handles?(cmd)
485:       return nil unless @commandmappers.has_key?(cmd.to_sym)
486:       return @commandmappers[cmd.to_sym][:botmodule]
487:     end

Private Instance methods

This method is the one that actually loads a module from the file fname

desc is a simple description of what we are loading (plugin/botmodule/whatever)

It returns the Symbol :loaded on success, and an Exception on failure

[Source]

     # File lib/rbot/plugins.rb, line 559
559:     def load_botmodule_file(fname, desc=nil)
560:       # create a new, anonymous module to "house" the plugin
561:       # the idea here is to prevent namespace pollution. perhaps there
562:       # is another way?
563:       plugin_module = Module.new
564:       # each plugin uses its own textdomain, we bind it automatically here
565:       bindtextdomain_to(plugin_module, "rbot-#{File.basename(fname, '.rb')}")
566: 
567:       desc = desc.to_s + " " if desc
568: 
569:       begin
570:         plugin_string = IO.read(fname)
571:         debug "loading #{desc}#{fname}"
572:         plugin_module.module_eval(plugin_string, fname)
573:         return :loaded
574:       rescue Exception => err
575:         # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
576:         error report_error("#{desc}#{fname} load failed", err)
577:         bt = err.backtrace.select { |line|
578:           line.match(/^(\(eval\)|#{fname}):\d+/)
579:         }
580:         bt.map! { |el|
581:           el.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
582:             "#{fname}#{$1}#{$3}"
583:           }
584:         }
585:         msg = err.to_s.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
586:           "#{fname}#{$1}#{$3}"
587:         }
588:         begin
589:           newerr = err.class.new(msg)
590:         rescue ArgumentError => err_in_err
591:           # Somebody should hang the ActiveSupport developers by their balls
592:           # with barbed wire. Their MissingSourceFile extension to LoadError
593:           # _expects_ a second argument, breaking the usual Exception interface
594:           # (instead, the smart thing to do would have been to make the second
595:           # parameter optional and run the code in the from_message method if
596:           # it was missing).
597:           # Anyway, we try to cope with this in the simplest possible way. On
598:           # the upside, this new block can be extended to handle other similar
599:           # idiotic approaches
600:           if err.class.respond_to? :from_message
601:             newerr = err.class.from_message(msg)
602:           else
603:             raise err_in_err
604:           end
605:         end
606:         newerr.set_backtrace(bt)
607:         return newerr
608:       end
609:     end

[Validate]