# dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
#
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
module DBus
# Regular expressions that should match all method names.
METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/
# Regular expressions that should match all interface names.
INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/
# Exception raised when an invalid class definition is encountered.
class InvalidClassDefinition < Exception
end
# = D-Bus interface class
#
# This class is the interface descriptor. In most cases, the Introspect()
# method call instantiates and configures this class for us.
#
# It also is the local definition of interface exported by the program.
# At the client side, see ProxyObjectInterface
class Interface
# The name of the interface. String
attr_reader :name
# The methods that are part of the interface. Hash: Symbol => DBus::Method
attr_reader :methods
# The signals that are part of the interface. Hash: Symbol => Signal
attr_reader :signals
# Creates a new interface with a given _name_.
def initialize(name)
validate_name(name)
@name = name
@methods = {}
@signals = {}
end
# Validates a service _name_.
def validate_name(name)
raise InvalidIntrospectionData if name.bytesize > 255
raise InvalidIntrospectionData if name =~ /^\./ || name =~ /\.$/
raise InvalidIntrospectionData if name =~ /\.\./
raise InvalidIntrospectionData if name !~ /\./
name.split(".").each do |element|
raise InvalidIntrospectionData if element !~ INTERFACE_ELEMENT_RE
end
end
# Helper method for defining a method _m_.
def define(m)
if m.is_a?(Method)
@methods[m.name.to_sym] = m
elsif m.is_a?(Signal)
@signals[m.name.to_sym] = m
end
end
alias << define
# Defines a method with name _id_ and a given _prototype_ in the
# interface.
def define_method(id, prototype)
m = Method.new(id)
m.from_prototype(prototype)
define(m)
end
end # class Interface
# = A formal parameter has a name and a type
class FormalParameter
attr_reader :name
attr_reader :type
def initialize(name, type)
@name = name
@type = type
end
# backward compatibility, deprecated
def [](index)
case index
when 0 then name
when 1 then type
end
end
end
# = D-Bus interface element class
#
# This is a generic class for entities that are part of the interface
# such as methods and signals.
class InterfaceElement
# The name of the interface element. Symbol
attr_reader :name
# The parameters of the interface element. Array: FormalParameter
attr_reader :params
# Validates element _name_.
def validate_name(name)
return if (name =~ METHOD_SIGNAL_RE) && (name.bytesize <= 255)
raise InvalidMethodName, name
end
# Creates a new element with the given _name_.
def initialize(name)
validate_name(name.to_s)
@name = name
@params = []
end
# Adds a formal parameter with _name_ and _signature_
# (See also Message#add_param which takes signature+value)
def add_fparam(name, signature)
@params << FormalParameter.new(name, signature)
end
# Deprecated, for backward compatibility
def add_param(name_signature_pair)
add_fparam(*name_signature_pair)
end
end # class InterfaceElement
# = D-Bus interface method class
#
# This is a class representing methods that are part of an interface.
class Method < InterfaceElement
# The list of return values for the method. Array: FormalParameter
attr_reader :rets
# Creates a new method interface element with the given _name_.
def initialize(name)
super(name)
@rets = []
end
# Add a return value _name_ and _signature_.
def add_return(name, signature)
@rets << FormalParameter.new(name, signature)
end
# Add parameter types by parsing the given _prototype_.
def from_prototype(prototype)
prototype.split(/, */).each do |arg|
arg = arg.split(" ")
raise InvalidClassDefinition if arg.size != 2
dir, arg = arg
if arg =~ /:/
arg = arg.split(":")
name, sig = arg
else
sig = arg
end
case dir
when "in"
add_fparam(name, sig)
when "out"
add_return(name, sig)
end
end
self
end
# Return an XML string representation of the method interface elment.
def to_xml
xml = %(\n)
@params.each do |param|
name = param.name ? %(name="#{param.name}" ) : ""
xml += %(\n)
end
@rets.each do |param|
name = param.name ? %(name="#{param.name}" ) : ""
xml += %(\n)
end
xml += %(\n)
xml
end
end # class Method
# = D-Bus interface signal class
#
# This is a class representing signals that are part of an interface.
class Signal < InterfaceElement
# Add parameter types based on the given _prototype_.
def from_prototype(prototype)
prototype.split(/, */).each do |arg|
if arg =~ /:/
arg = arg.split(":")
name, sig = arg
else
sig = arg
end
add_fparam(name, sig)
end
self
end
# Return an XML string representation of the signal interface elment.
def to_xml
xml = %(\n)
@params.each do |param|
name = param.name ? %(name="#{param.name}" ) : ""
xml += %(\n)
end
xml += %(\n)
xml
end
end # class Signal
end # module DBus