From: Guillaume Marcais Date: 2004-10-29T02:06:17+09:00 Subject: Re: Ruby/DL (was Re: The real Ruby vs. Python.) --=-XN87+79wxRM1KhQCmcRq Content-Type: text/plain Content-Transfer-Encoding: 7bit On Wed, 2004-10-27 at 17:53, Jamis Buck wrote: > Besides which: Ruby/DL is often touted as a great way to write extension > libraries. Are there any examples of libraries that have been written > using Ruby/DL instead of C? I've never seen any, much to my own > frustration when I would like a good example of Ruby/DL usage. :( I wouldn't dare to brag about 'good example', but it at least is a (mostly) working one. I say mostly as a good number of the settings for a user are undocumented and I had to guess the return value type. Anyway, the following code is a binding to the Merak mail server library. It allowed may to automate the transition from our old mail server to Merak. It is now a nice way to get the password in clear that the stupid GUI shows as stars. The creation and basic settings of a Domain and a User should work. The more exotic settings are untested. Here is an example of a session using the created merak shell: $ ssh Administrator@fakedomain irb -r merak_shell Administrator@fakedomain's password: Warning: Remote host denied X11 forwarding. Merak 1:0> toto = Domain["toto.net"] toto = Domain["toto.net"] => # Merak 2:0> user = toto["toto"] user = toto["toto"] => # Merak 3:0> user.password user.password => "coucou" Merak 4:0> user.password = "hello" user.password = "hello" => "hello" Merak 5:0> user.save user.save => # In short, I would never have written it if I had to drop down to C. One reason being that I don't have a compiler installed on any Windows machine yet. Guillaume. --=-XN87+79wxRM1KhQCmcRq Content-Disposition: attachment; filename=merak.rb Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=ISO-8859-1 require 'Win32API' module Merak class Error < ScriptError; end class APIError < Error; end class Failure < APIError; CODE =3D -1; end class License < APIError; CODE =3D -2; end class Params < APIError; CODE =3D -3; end class Paths < APIError; CODE =3D -4; end class Config < APIError; CODE =3D -5; end ERRORS =3D [nil, Failure, License, Params, Paths, Config] # Call function in the DLL. Return an error object if an error occures. class APIClass attr_reader :winapis DLL =3D 'API.dll' ARGS =3D { :Init =3D> ["P"],=20 :GetDomainCount =3D> [], :GetDomainList =3D> ["P", "L"], :AddDomain =3D> ["P", "P", "L"], :DeleteDomain =3D> ["L"], :GetDomainIndex =3D> ["P"], :GetDomainName =3D> ["L", "P", "L"], :LoadDomain =3D> ["L", "P", "L"], :SaveDomain =3D> ["L", "P", "L"], :SetDomainDefaults =3D> ["P", "L"], :GetDomainIP =3D> ["L", "P", "L"], :SetDomainIP =3D> ["L", "P", "L"], :GetDomainSetting =3D> ["P", "L", "L", "P", "L"], :SetDomainSetting =3D> ["P", "L", "L", "P", "L"], :GetUserCount =3D> ["P"], :GetUserList =3D> ["P", "P", "L"], :GetUserIndex =3D> ["P", "P"], :AddUser =3D> ["P", "P", "L"], :DeleteUser =3D> ["P", "L"], :SetUserDefaults =3D> ["P", "L"], :LoadUser =3D> ["P", "L", "P", "L"], :SaveUser =3D> ["P", "L", "P", "L"], :GetUserSetting =3D> ["P", "L", "L", "P", "L"], :SetUserSetting =3D> ["P", "L", "L", "P", "L"], } def initialize; @winapis =3D {}; end def method_missing(call, *args) winapi =3D @winapis[call] if winapi.nil? api_args =3D ARGS[call] raise Error, "Unknown API call '#{call}'" if api_args.nil? winapi =3D @winapis[call] =3D Win32API.new(DLL, call.to_s, api_args= , "L") end param =3D args.last.kind_of?(Hash) ? args.pop : nil r =3D winapi.call(*args) if r < 0 msg =3D "Error calling #{call}(#{args.join(", ")})" pmsg =3D param ? param[("err_%d" % (-r)).intern] : nil msg <<=3D "\n#{pmsg}" if pmsg raise ERRORS[-r], msg end r end end # API =20 # Different types to store in Merak fields # Should implement pack, unpack, size and base_size # The virtual type must be instanciate with a base_size argument class BoolT def self.pack(b); [b ? 1 : 0].pack("C"); end def self.unpack(s); s.unpack("C")[0] !=3D 0; end def self.size(s); 1; end def self.base_size; 1; end end =20 class NumT def self.pack(n); [n].pack("L"); end def self.unpack(s); s.unpack("L")[0]; end def self.size(s); 4; end def self.base_size; 4; end end =20 class TimeT def self.pack(n); nil; end def self.unpack(s); nil; end def self.size(s); 8; end def self.base_size; 8; end end =20 class StringTVirtual attr_accessor :base_size def initialize(base_size); @base_size =3D base_size; end def pack(s); s; end def unpack(s); s; end def size(s); s.size; end end =20 class ListTVirtual attr_accessor :base_size def initialize(base_size); @base_size =3D base_size; end def pack(l); l.kind_of?(Array) ? l.join('; ') : l; end def unpack(s); s.split(/[,;]/).collect { |x| x.strip }; end def size(s); s.size; end end class TypeTVirtual attr_reader :category def initialize(category); @category =3D category; end def pack(t); [@category.index(t)].pack("L"); end def unpack(s); @category[s.unpack("L")[0]]; s.unpack("L")[0]; end def size(s); 4; end def base_size; 4; end end # Actual type of virtual definition String32T =3D StringTVirtual.new(32) String128T =3D StringTVirtual.new(128) String1024T =3D StringTVirtual.new(1024) List128T =3D ListTVirtual.new(128) List1024T =3D ListTVirtual.new(1024) =20 # Get/Set settings for object module Settings class << self alias :__append_features :append_features def append_features(mod) __append_features(mod) mod.const_get(:SETTINGS_DESC).each_key do |k| mod.module_eval("def #{k}; get_setting(:#{k}); end") mod.module_eval("def #{k}=3D(val); set_setting(:#{k}, val); end") end end end def get_setting(setting, b_size =3D nil) s_desc =3D settings_desc[setting] raise Error, "Unknown setting '#{setting}'" if s_desc.nil? get_setting =3D settings_calls[:get_setting] =20 args =3D [@buffer, @buffer.size, s_desc[:val], :buffer, :size] r, buffer, args =3D call_retry(get_setting, s_desc[:type].base_size, = args) return s_desc[:type].unpack(buffer[0, r]) end private :get_setting def set_setting(setting, value) s_desc =3D settings_desc[setting] raise Error, "Unknown setting '#{setting}'" if s_desc.nil? set_setting =3D settings_calls[:set_setting] s_buf =3D s_desc[:type].pack(value) r =3D API.send(set_setting, @buffer, @buffer.size, s_desc[:val], s_buf, s_buf.size) return value end private :set_setting end # module Settings module Utils # Call an API methods, max attempts time, increasing the size of a buff= er # The buffer is located at :buffer. # Return: [result, buffer, args] def call_retry(call, size_base, args, attempts =3D 5) b_index =3D args.index(:buffer) s_index =3D args.index(:size) return nil if b_index.nil? =20 i =3D 1 begin args[b_index] =3D buffer =3D " " * (i * size_base) args[s_index] =3D buffer.size if s_index r =3D API.__send__(call, *args) rescue Params =3D> e i +=3D 1 raise e if i > attempts retry end =20 return [r, buffer, args] end module_function :call_retry end class Domain include Utils class << self private :new =20 def names count =3D API.GetDomainCount base =3D count * 50 r, buf, args =3D Utils.call_retry(:GetDomainList, base, [:buffer, := size]) buf[0, r].split("\0") end # Get by index or name def get(ident) case ident when Integer begin r, name, a =3D Utils.call_retry(:GetDomainName, 50,=20 [ident, :buffer, :size]) name =3D name[0, r] rescue Params raise Error, "No such domain with index '#{ident}'" end index =3D ident when String index =3D API.GetDomainIndex(ident,=20 :err_1 =3D> "No such domain '#{ident}'= ") name =3D ident end =20 res =3D new(index, nil, name) res.reload res end=0D alias :[] :get def create(name) r, buffer, args =3D Utils.call_retry(:SetDomainDefaults, 4192,=20 [:buffer, :size]) API.AddDomain(name, buffer, buffer.size) index =3D API.GetDomainIndex(name) new(index, buffer, name) end def delete(ident); get(ident).delete; end end # class << Domain =20 attr_reader :index def initialize(index, buffer =3D nil, name =3D nil) @index, @buffer, @name =3D index, buffer, name end =20 # Returns Virtual Binding IP, or nil if none def ip ip =3D " " * 16 r =3D API.GetDomainIP(@index, ip, ip.size) return (r =3D=3D 0) ? nil : ip[0, r] end def ip=3D(ip) ip ||=3D "" API.SetDomainIP(@index, ip, ip.size, :err_1 =3D> "Bad IP '#{ip}'") ip end def save return true unless @buffer API.SaveDomain(@index, @buffer, @buffer.size) self end def delete; API.DeleteDomain(@index); self; end def name return @name if @name r, name, args =3D call_retry(:GetDomainName, 50, [@index, :buffer, :s= ize]) @name =3D name[0, r] @name end def inspect; "#"; end SETTINGS_DESC =3D { :description =3D> { :val =3D> 0, :type =3D> String1024T }, :type =3D> { :val =3D> 1, :type =3D> NumT }, :domain_value =3D> { :val =3D> 3, :type =3D> String1024T }, :postmaster =3D> { :val =3D> 4, :type =3D> List1024T }, :admin_forward =3D> { :val =3D> 5, :type =3D> String1024T }, :unknown_users_forward =3D> { :val =3D> 6, :type =3D> BoolT }, :unknown_forward_to =3D> { :val =3D> 7, :type =3D> List1024T }, :info_to_admin =3D> { :val =3D> 8, :type =3D> BoolT }, } def settings_desc; SETTINGS_DESC; end private :settings_desc SETTINGS_CALLS =3D {=20 :get_setting =3D> :GetDomainSetting, :set_setting =3D> :SetDomainSett= ing=20 } def settings_calls; SETTINGS_CALLS; end private :settings_calls include Settings =20 def reload args =3D [@index, :buffer, :size] r, buffer, args =3D call_retry(:LoadDomain, 4192, args) @buffer =3D buffer r end # User management def users count =3D API.GetUserCount(@name) base =3D count * 50 r, buf, args =3D call_retry(:GetUserList, base, [@name, :buffer, :siz= e]) buf[0, r].split("\0") end def get_user(ident) case ident when Integer index =3D ident when String index =3D API.GetUserIndex(@name, ident) end r, buf, args =3D call_retry(:LoadUser, 4192,=20 [@name, index, :buffer, :size]) User.new(self, index, buf) end=0D alias :[] :get_user def create_user(*aliases) r, buffer, args =3D Utils.call_retry(:SetUserDefaults, 4192,=20 [:buffer, :size]) u =3D User.new(self, nil, buffer) u.alias =3D aliases unless aliases.empty? u end alias :add_user :create_user end # Domain class User include Utils =0D attr_reader :index, :buffer, :domain def initialize(domain, index, buf) @index, @buffer, @domain =3D index, buf, domain end def inspect "#" end # class POP3; end # class IMAP; end # class IMAPandPOP3; end # UserTypeT =3D TypeTVirtual.new([POP3, IMAP, IMAPandPOP3]) SETTINGS_DESC =3D { :type =3D> { :val =3D> 0, :type =3D> NumT }, :anti_spam_index =3D> { :val =3D> 1, :type =3D> NumT }, :name =3D> { :val =3D> 2, :type =3D> String32T }, :alias =3D> { :val =3D> 3, :type =3D> List128T }, :mailbox =3D> { :val =3D> 16, :type =3D> String32T }, :account_disabled =3D> { :val =3D> 17, :type =3D> BoolT }, :account_valid =3D> { :val =3D> 18, :type =3D> BoolT }, :account_valid_till =3D> { :val =3D> 19, :type =3D> TimeT }, :check_virus =3D> { :val =3D> 20, :type =3D> BoolT }, :allow_remote =3D> { :val =3D> 21, :type =3D> BoolT }, :validity_report =3D> { :val =3D> 22, :type =3D> BoolT }, :validity_report_days =3D> { :val =3D> 23, :type =3D> NumT }, :nt_Password =3D> { :val =3D> 24, :type =3D> BoolT }, :imap =3D> { :val =3D> 25, :type =3D> BoolT }, :imap_mailbox =3D> { :val =3D> 26, :type =3D> String32T }, :max_message_size =3D> { :val =3D> 27, :type =3D> NumT }, :dont_show_messages =3D> { :val =3D> 28, :type =3D> BoolT }, :any_password =3D> { :val =3D> 29, :type =3D> BoolT }, :etrn =3D> { :val =3D> 30, :type =3D> BoolT }, :delete_expire =3D> { :val =3D> 31, :type =3D> BoolT }, :null =3D> { :val =3D> 32, :type =3D> BoolT }, :password =3D> { :val =3D> 33, :type =3D> String32T }, :nt_password_value =3D> { :val =3D> 34, :type =3D> String32T }, :domain_admin_index =3D> { :val =3D> 35, :type =3D> NumT }, :domain_admin =3D> { :val =3D> 36, :type =3D> BoolT }, :mail_box_path =3D> { :val =3D> 37, :type =3D> String128T }, :admin =3D> { :val =3D> 38, :type =3D> BoolT }, :max_box =3D> { :val =3D> 39, :type =3D> NumT }, :max_box_size =3D> { :val =3D> 40, :type =3D> NumT }, :force_from =3D> { :val =3D> 41, :type =3D> String32T }, :respond =3D> { :val =3D> 42, :type =3D> NumT }, :only_local_domain =3D> { :val =3D> 43, :type =3D> BoolT }, :use_remote_address =3D> { :val =3D> 44, :type =3D> String32T }, :forward_to =3D> { :val =3D> 45, :type =3D> String32T }, :respond_with =3D> { :val =3D> 46, :type =3D> String1024T }, :mail_in =3D> { :val =3D> 47, :type =3D> String32T }, :mail_out =3D> { :val =3D> 48, :type =3D> String32T }, :valid_report =3D> { :val =3D> 49, :type =3D> BoolT }, :delete_older =3D> { :val =3D> 50, :type =3D> BoolT }, :delete_older_days =3D> { :val =3D> 51, :type =3D> NumT }, :forward_older =3D> { :val =3D> 52, :type =3D> BoolT }, :forward_older_days =3D> { :val =3D> 53, :type =3D> NumT }, :forward_older_to =3D> { :val =3D> 54, :type =3D> String32T }, :remote_address =3D> { :val =3D> 55, :type =3D> String32T }, :force_from_address =3D> { :val =3D> 56, :type =3D> BoolT }, :megabyte_send_limit =3D> { :val =3D> 57, :type =3D> NumT }, :number_send_limit =3D> { :val =3D> 58, :type =3D> NumT }, } def settings_desc; SETTINGS_DESC; end private :settings_desc SETTINGS_CALLS =3D { :get_setting =3D> :GetUserSetting, :set_setting =3D> :SetUserSetting=20 } def settings_calls; SETTINGS_CALLS; end private :settings_calls include Settings def save return true unless @buffer if @index API.SaveUser(@domain.name, @index, @buffer, @buffer.size) else r =3D API.AddUser(@domain.name, @buffer, @buffer.size) @index =3D r end self end =20 def delete; API.DeleteUser(@domain.name, @index); end end # User =20 def self.init(path) const_set("API", APIClass.new) API.Init(path) end end --=-XN87+79wxRM1KhQCmcRq Content-Disposition: attachment; filename=merak_shell.rb Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=ISO-8859-1 prompt =3D "Merak %3n:%i" IRB.conf[:PROMPT][:Merak_Prompt] =3D { :PROMPT_I =3D> "#{prompt}> ", :PROMPT_C =3D> "#{prompt}* ", :PROMPT_S =3D> "#{prompt}* ", :RETURN =3D> "=3D> %s\n", } IRB.conf[:PROMPT_MODE] =3D :Merak_Prompt require 'merak' include Merak Merak.init('C:\Program Files\Merak') --=-XN87+79wxRM1KhQCmcRq Content-Disposition: attachment; filename="Merak Shell.bat" Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=ISO-8859-1 @echo OFF=0D cd \cygwin\home\ADMINI~1=0D irb -r merak_shell=0D --=-XN87+79wxRM1KhQCmcRq--