0
+require 'rpm_interface'
0
+ attr_accessor :signatures, :node_kind, :pkgs, :tags, :roles, :extra_config, :exception_handler, :exit_after_current_dispatch
0
+ self.extra_config = nil
0
+ self.exception_handler = nil
0
+ self.exit_after_current_dispatch = false
0
+ # Define a handler method
0
+ # +method+ is the Symbol method name
0
+ # +names+ is a list of required parameters
0
+ def self.handle(method, *names, &block)
0
+ Chassis.signatures[method] = names.sort { |a, b| a.to_s <=> b.to_s }
0
+ define_method("handle_proxy_#{method}", &block)
0
+ define_method("handle_#{method}".to_sym) do |iargs|
0
+ args = self.convert_args(iargs)
0
+ self.verify_args(method, args)
0
+ self.send("handle_proxy_#{method}", args)
0
+ # Specify this handler's kind
0
+ # +name+ is a String identifier
0
+ Chassis.node_kind = name
0
+ # Specify package dependencies for the details list. If this is called
0
+ # more than once, the first list that contains a full match for installed
0
+ # packages will be used.
0
+ # +names+ is a list of package names (their dependencies will be
0
+ # traced automatically)
0
+ def self.packages(*names)
0
+ return if !Chassis.pkgs.empty?
0
+ fully_installed = names.all? do |package|
0
+ Powerset::PowerPack.installed?(package)
0
+ deps = names.inject([]) do |acc, package|
0
+ acc.concat Powerset::PowerPack.check(package).package_dependencies
0
+ packs = Powerset::PowerPack.check_packages(deps.uniq)
0
+ Chassis.pkgs = packs.compact.map { |p| [p.name, self.limit_version(p.version)] }
0
+ def self.config(&block)
0
+ Chassis.extra_config = block.call
0
+ # Specify a block that takes an Exception as its single argument
0
+ # to be called when an exception is not handled inside a handler
0
+ # +block+ is the block to execute
0
+ # [:result, <jsonified result>]
0
+ # [:error, <error string>]
0
+ def self.handle_exception(&block)
0
+ Chassis.exception_handler = block
0
+ # Limit version number to 9999 max
0
+ # +version+ is the verstion number to limit
0
+ def self.limit_version(version)
0
+ version.map { |x| x > 9999 ? 9999 : x }
0
+ # Convert Erlang-style args to Ruby-style args
0
+ # +iargs+ is the Erlang-style arg structure
0
+ # Returns Hash (converted args)
0
+ def convert_args(iargs)
0
+ args = HashWithIndifferentAccess.new
0
+ args[a[0]] = self.convert_args_node(a[1])
0
+ # Helper for +convert_args+. Recursively converts a node in
0
+ # Erlang-style to a node in Ruby-style
0
+ # +node+ is the Erlang-style node
0
+ # Returns String|Symbol|Hash|Array
0
+ # Raises if an invalid node is encountered
0
+ def convert_args_node(node)
0
+ if node.kind_of?(String) || node.kind_of?(Symbol) || node.kind_of?(Numeric) ||
0
+ node.kind_of?(TrueClass) || node.kind_of?(FalseClass) || node.kind_of?(NilClass)
0
+ elsif node.instance_of?(Array)
0
+ node[1].inject(HashWithIndifferentAccess.new) do |acc, x|
0
+ acc[x[0]] = convert_args_node(x[1]); acc
0
+ elsif node[0] == :array
0
+ node[1].inject([]) do |acc, x|
0
+ acc << convert_args_node(x)
0
+ raise "Invalid tagged node: #{node.inspect}"
0
+ raise "Invalid node, must be an instance of String, Symbol, Numeric, True, False, Nil, or Array: #{node.inspect} (#{node.class.inspect})"
0
+ # Verify that the required args are met for the given method call
0
+ # +method+ is the Symbol representing the method name
0
+ # +args+ is the Ruby-style args to check
0
+ # Raises on verification failure
0
+ def verify_args(method, args)
0
+ matches = Chassis.signatures[method].select do |key|
0
+ misses = Chassis.signatures[method] - matches
0
+ raise "Required arguments missing for '#{method}': #{misses.join(", ")}"
0
+ # Dispatch a method by its name
0
+ # +method+ is the Symbol representing the method name
0
+ # +retype+ is the response type Symbol (:json | :pure)
0
+ # +args+ is the Erlang-stle args for the call
0
+ # [:result, <jsonified result>]
0
+ # [:error, <error string>]
0
+ def dispatch(method, retype, args)
0
+ result = self.send("handle_#{method}".to_sym, args)
0
+ result_key = Chassis.exit_after_current_dispatch ? :last_result : :result
0
+ [result_key, [:raw, result.to_json]]
0
+ raise "Unknown response type: #{retype}"
0
+ if e.instance_of?(SystemExit)
0
+ elsif Chassis.exception_handler
0
+ Chassis.exception_handler.call(e)
0
+ rescue Exception => e2
0
+ [:error, e2.message + "\n\n" + e2.backtrace.join("\n")]
0
+ [:error, e.message + "\n\n" + e.backtrace.join("\n")]
0
+ # The API structure of this Chassis, sorted alphabetically by method name
0
+ # Returns Array (Erlang-style nested structure)
0
+ # [:meth2, [:arg1, :arg2]]]
0
+ api = Chassis.signatures.to_a.sort { |a, b| a.first.to_s <=> b.first.to_s }
0
+ # Construct the config (aka details)
0
+ # Returns Array (config)
0
+ details = Chassis.pkgs.dup
0
+ details << ["tags"] + Chassis.tags unless Chassis.tags.empty?
0
+ details << ["roles"] + Chassis.roles unless Chassis.roles.empty?
0
+ details << ["kind"] + [Chassis.node_kind] if Chassis.node_kind
0
+ details << Chassis.extra_config if Chassis.extra_config
0
+ # Start the Erlectricity recieve/respond loop
0
+ receive(IO.new(3), IO.new(4)) do |f|
0
+ f.when(:call, Array) do |args|
0
+ f.send! self.dispatch(method, retype, args)
0
+ exit if Chassis.exit_after_current_dispatch
0
+ f.send! :result, self.config
0
+ f.send! :result, self.api
0
+ def self.pull_cli_args
0
+ opts = OptionParser.new
0
+ opts.on("--tags x,y,z", Array) do |val|
0
+ opts.on("--roles x,y,z", Array) do |val|
0
+ dashdash = ARGV.index('--')
0
+ extras = ARGV[(dashdash + 1)..-1]
0
+ # ramrod the extras into ARGV
0
+ ObjectSpace.each_object(Class) do |o|
0
+ handlers << o if o < Chassis
0
+ # check that one and only one exist
0
+ raise "There must be exactly one class that extends Chassis, but there are #{handlers.size}"
0
+ # check that a kind has been set
0
+ unless Chassis.node_kind
0
+ raise "A kind must be specified"
0
+ h = handlers.first.new
0
+ def return_and_exit(data)
0
+ Chassis.exit_after_current_dispatch = true
0
+class HashWithIndifferentAccess < Hash
0
+ def initialize(constructor = {})
0
+ if constructor.is_a?(Hash)
0
+ def default(key = nil)
0
+ if key.is_a?(Symbol) && include?(key = key.to_s)
0
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
0
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
0
+ regular_writer(convert_key(key), convert_value(value))
0
+ def update(other_hash)
0
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
0
+ alias_method :merge!, :update
0
+ super(convert_key(key))
0
+ alias_method :include?, :key?
0
+ alias_method :has_key?, :key?
0
+ alias_method :member?, :key?
0
+ def fetch(key, *extras)
0
+ super(convert_key(key), *extras)
0
+ def values_at(*indices)
0
+ indices.collect {|key| self[convert_key(key)]}
0
+ HashWithIndifferentAccess.new(self)
0
+ super(convert_key(key))
0
+ def stringify_keys!; self end
0
+ def symbolize_keys!; self end
0
+ def to_options!; self end
0
+ # Convert to a Hash with String keys.
0
+ Hash.new(default).merge(self)
0
+ key.kind_of?(Symbol) ? key.to_s : key
0
+ def convert_value(value)
0
+ value.with_indifferent_access
0
+ value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
0
+ def with_indifferent_access
0
+ hash = HashWithIndifferentAccess.new(self)
0
+ hash.default = self.default
0
+#############################################################################
0
+ class Awesome < Chassis
0
+ packages "powerset_ruby", "tk"
0
+ ["grammar", "/tmp/awesome.lfg"]
0
+ handle(:beta, :foo, :bar) do |args|
0
+ helper(args[:foo], args[:bar])
0
+ raise "once more unto the breach"
0
+ return_and_exit('foo')
0
+ handle_exception do |e|
0
+ class TestChassis < Test::Unit::TestCase
0
+ def test_signatures_should_match_declaration
0
+ sigs = {:gamma=>[], :alpha=>[], :delta=>[], :beta=>[:bar, :foo], :epsilon=>[]}
0
+ assert_equal sigs, Chassis.signatures
0
+ def test_handler_methods_should_exist
0
+ assert @a.respond_to?(:handle_alpha)
0
+ assert @a.respond_to?(:handle_beta)
0
+ def test_handler_methods_arity_should_always_be_one
0
+ assert 1, @a.method(:handle_alpha).arity
0
+ assert 1, @a.method(:handle_beta).arity
0
+ def test_convert_args_should_convert_top_level_array_to_hash
0
+ i = [[:foo, 'bar'], [:qux, 'quux']]
0
+ o = {'foo' => 'bar', 'qux' => 'quux'}
0
+ assert_equal o, @a.convert_args(i)
0
+ i = [['foo', 'bar'], ['qux', 'quux']]
0
+ o = {'foo' => 'bar', 'qux' => 'quux'}
0
+ assert_equal o, @a.convert_args(i)
0
+ def test_convert_args_should_convert_structs_to_indifferent_hashes
0
+ i = [[:foo, 'bar'], [:baz, [:struct, [[:qux, 'quux']]]]]
0
+ o = {'foo' => 'bar', 'baz' => {'qux' => 'quux'}}
0
+ assert_equal o, @a.convert_args(i)
0
+ i = [[:foo, 'bar'], [:baz, [:struct, [[:qux, [:struct, [[:a, 'b']]]]]]]]
0
+ o = {'foo' => 'bar', 'baz' => {'qux' => {'a' => 'b'}}}
0
+ assert_equal o, @a.convert_args(i)
0
+ def test_convert_args_should_make_all_hashes_indifferent
0
+ i = [[:foo, 'bar'], [:baz, [:struct, [[:qux, 'quux']]]]]
0
+ # o = {'foo' => 'bar', 'baz' => {'qux' => 'quux'}}
0
+ o = @a.convert_args(i)
0
+ assert_equal 'bar', o[:foo]
0
+ assert_equal 'bar', o['foo']
0
+ assert_equal 'quux', o[:baz][:qux]
0
+ assert_equal 'quux', o['baz']['qux']
0
+ def test_convert_args_should_convert_arrays_to_arrays
0
+ i = [[:foo, 'bar'], [:baz, [:array, [:qux, 'quux']]]]
0
+ o = {'foo' => 'bar', 'baz' => [:qux, 'quux']}
0
+ assert_equal o, @a.convert_args(i)
0
+ i = [[:foo, 'bar'], [:baz, [:array, [:qux, [:array, [:a, 'b']]]]]]
0
+ o = {'foo' => 'bar', 'baz' => [:qux, [:a, 'b']]}
0
+ assert_equal o, @a.convert_args(i)
0
+ def test_convert_node_should_be_identity_for_strings
0
+ assert_equal 'foo', @a.convert_args_node('foo')
0
+ def test_convert_node_should_be_identity_for_symbol
0
+ assert_equal :foo, @a.convert_args_node(:foo)
0
+ def test_convert_node_should_be_identity_for_true
0
+ assert_equal true, @a.convert_args_node(true)
0
+ def test_convert_node_should_be_identity_for_false
0
+ assert_equal false, @a.convert_args_node(false)
0
+ def test_convert_node_should_be_identity_for_nil
0
+ assert_equal nil, @a.convert_args_node(nil)
0
+ def test_convert_node_should_be_identity_for_numeric
0
+ assert_equal 1, @a.convert_args_node(1)
0
+ assert_equal 1.0, @a.convert_args_node(1.0)
0
+ assert_equal 9999999999999999999, @a.convert_args_node(9999999999999999999)
0
+ def test_convert_node_should_convert_array
0
+ assert_equal [1], @a.convert_args_node([:array, [1]])
0
+ assert_equal [1, 2], @a.convert_args_node([:array, [1, 2]])
0
+ assert_equal [1, 2, 3], @a.convert_args_node([:array, [1, 2, 3]])
0
+ def test_convert_node_should_convert_nested_array
0
+ assert_equal [[1]], @a.convert_args_node([:array, [[:array, [1]]]])
0
+ def test_convert_node_should_convert_struct_to_hash
0
+ i = [:struct, [[:foo, 'foo']]]
0
+ assert_equal o, @a.convert_args_node(i)
0
+ def test_alpha_nominal
0
+ assert_equal 'alpha', @a.handle_alpha([])
0
+ assert_equal 'foo=bar', @a.handle_beta([[:foo, 'foo'], [:bar, 'bar']])
0
+ def test_beta_with_missing_required_arg_should_raise
0
+ assert_raise RuntimeError do
0
+ @a.handle_beta([[:foo, 'foo']])
0
+ def test_epsilon_should_set_exit_after_current_dispatch
0
+ assert_equal false, Chassis.exit_after_current_dispatch
0
+ @a.dispatch(:epsilon, :json, [])
0
+ assert_equal true, Chassis.exit_after_current_dispatch
0
+ # prevent from exiting
0
+ Chassis.exit_after_current_dispatch = false
0
+ def test_epsilon_should_respond_with_last_result
0
+ res = @a.dispatch(:epsilon, :json, [])
0
+ assert_equal [:last_result, [:raw, "\"foo\""]], res
0
+ # prevent from exiting
0
+ Chassis.exit_after_current_dispatch = false
0
+ assert_equal [[:alpha, []], [:beta, [:bar, :foo]], [:delta, []], [:epsilon, []], [:gamma, []]], @a.api
0
+ assert_equal "awesome", Chassis.node_kind
0
+ packages = [["powerset_ruby", [1, 8, 5, 9999]],
0
+ ["tk", [8, 4, 14, 0]],
0
+ ["readline", [5, 1, 4, 0]],
0
+ ["tcl", [8, 4, 14, 0]]]
0
+ assert_equal packages, Chassis.pkgs
0
+ def test_handle_exception
0
+ @a.dispatch(:gamma, :json, [])
0
+ assert_equal "EBORKD", e.message
0
+ def test_pull_cli_args
0
+ ARGV.concat(["--tags=foo,bar", "--", "--tags=baz,qux"])
0
+ assert_equal ["foo", "bar"], Chassis.tags
0
+ assert_equal ["--tags=baz,qux"], ARGV
0
+ ARGV.concat(["--tags=foo,bar", "--roles=production", "--", "--tags=baz,qux"])
0
+ config = [["powerset_ruby", [1, 8, 5, 9999]],
0
+ ["tk", [8, 4, 14, 0]],
0
+ ["readline", [5, 1, 4, 0]],
0
+ ["tcl", [8, 4, 14, 0]],
0
+ ["tags", "foo", "bar"],
0
+ ["roles", "production"],
0
+ ["grammar", "/tmp/awesome.lfg"]]
0
+ assert_equal config, @a.config
0
+ def test_return_and_exit
0
+ assert_equal false, Chassis.exit_after_current_dispatch
0
+ assert_equal 'foo', @a.return_and_exit('foo')
0
+ Chassis.exit_after_current_dispatch = false