krpc 0.1.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,47 +1,48 @@
1
1
  require 'krpc/core_extensions'
2
+ require 'nokogiri'
2
3
  require 'colorize'
3
4
 
4
5
  module KRPC
5
6
  module Doc
7
+ @docstr_infos = {}
8
+ @procedure_docstr_infos = {}
6
9
  class << self
7
- @@docstr_infos = {}
8
- @@procedure_docstr_infos = {}
9
10
 
10
- def docstring_for_method(method_owner, method_name)
11
+ def docstring_for_method(method_owner, method_name, is_print_xmldoc_summary = true)
11
12
  is_static, class_cls = method_owner.class == Class ? [true, method_owner] : [false, method_owner.class]
12
13
  service_module_name, class_name = ruby_class_to_pb_module_class_pair(class_cls)
13
14
  key = [service_module_name, is_static, class_name, method_name.to_s].hash
14
- if (@@docstr_infos.has_key? key)
15
- construct_docstring(*@@docstr_infos[key], true)
15
+ if @docstr_infos.has_key? key
16
+ construct_docstring(*@docstr_infos[key], true, is_print_xmldoc_summary)
16
17
  else
17
18
  "No docstring for #{class_cls.name}#{calc_separator(is_static)}#{method_name.to_s} method" +
18
- (method_owner.respond_to?(method_name) ? "" : "\nThere is no such method -- maybe a typo")
19
+ (method_owner.respond_to?(method_name) ? "" : "\nThere is no such method -- maybe a typo")
19
20
  end
20
21
  end
21
22
 
22
- def docstring_for_procedure(service_name, procedure_name)
23
+ def docstring_for_procedure(service_name, procedure_name, is_print_xmldoc_summary = true)
23
24
  key = [service_name, procedure_name].hash
24
- if (@@procedure_docstr_infos.has_key? key)
25
- construct_docstring(service_name, '.', procedure_name, *@@procedure_docstr_infos[key][3..-1], false)
25
+ if @procedure_docstr_infos.has_key? key
26
+ construct_docstring(service_name, '.', procedure_name, *@procedure_docstr_infos[key][3..-1], false, is_print_xmldoc_summary)
26
27
  else
27
28
  "No docstring for #{service_name}.#{procedure_name} procedure"
28
29
  end
29
30
  end
30
31
 
31
- def add_docstring_info(is_static, cls, method_name, service_name="", procedure_name="", param_names=[], param_types=[], param_default=[], return_type: nil)
32
+ def add_docstring_info(is_static, cls, method_name, service_name="", procedure_name="", param_names=[], param_types=[], param_default=[], return_type: nil, xmldoc: "")
32
33
  service_module_name = service_name == cls.class_name ? Services.class_name : service_name
33
34
  key0 = [service_name, procedure_name].hash
34
35
  key1 = [service_module_name, false, cls.class_name, method_name].hash
35
- val = [cls.krpc_name, calc_separator(is_static), method_name, param_names, param_types, param_default, return_type]
36
- @@docstr_infos[key1] = @@procedure_docstr_infos[key0] = val
36
+ val = [cls.krpc_name, calc_separator(is_static), method_name, param_names, param_types, param_default, return_type, xmldoc]
37
+ @docstr_infos[key1] = @procedure_docstr_infos[key0] = val
37
38
  if is_static
38
39
  key2 = [service_module_name, true, cls.class_name, method_name].hash
39
- @@docstr_infos[key2] = val
40
+ @docstr_infos[key2] = val
40
41
  end
41
42
  end
42
43
 
43
44
  def add_special_docstring_info(key, value)
44
- @@docstr_infos[key] = value
45
+ @docstr_infos[key] = value
45
46
  end
46
47
 
47
48
  private #----------------------------------
@@ -56,36 +57,106 @@ module KRPC
56
57
  def calc_separator(is_static)
57
58
  is_static ? '.' : '#'
58
59
  end
60
+
61
+ def construct_docstring(namespace, separator, name, param_names, param_types, param_default, return_type, xmldoc, is_hide_this_param, is_print_xmldoc_summary)
62
+ xmldoc = Nokogiri::XML(xmldoc)
63
+ xmldoc_summary = xmlElements2str(xmldoc.xpath("doc/summary").children, :light_blue, :light_green, :light_red)
64
+
65
+ xmldoc_returns = xmlElements2str(xmldoc.xpath("doc/returns").children, :blue, :green, :light_red)
66
+ xmldoc_returns = "- ".blue + xmldoc_returns unless xmldoc_returns.empty?
59
67
 
60
- def construct_docstring(namespace, separator, name, param_names, param_types, param_default, return_type, is_hide_this_param)
61
- def type2str(type)
62
- return "nil" if type.nil?
63
- return type.class_name if type.class == Class
64
- rt = type.ruby_type
65
- if type.is_a?(Types::EnumType) then "Enum" + rt.keys.to_s
66
- elsif type.is_a?(Types::ListType) ||
67
- type.is_a?(Types::SetType)
68
- "#{rt.class_name}[#{type2str(type.value_type)}]"
69
- elsif type.is_a?(Types::DictionaryType)
70
- %Q{#{rt.class_name}[#{type2str(type.key_type)} => #{type2str(type.value_type)}]}
71
- elsif type.is_a?(Types::TupleType)
72
- %Q{#{rt.class_name}[#{type.value_types.map{|x| type2str(x)}.join(", ")}]}
73
- else rt.class_name end
68
+ xmldoc_params = {}
69
+ xmldoc.xpath("doc/param").each do |e|
70
+ param_name = e.attr("name")
71
+ if param_name
72
+ unless e.text.strip.empty?
73
+ xmldoc_params[param_name.underscore] = " - ".blue + xmlElements2str(e.children, :blue, :green, :yellow)
74
+ else
75
+ xmldoc_params[param_name.underscore] = ""
76
+ end
77
+ end
74
78
  end
79
+
75
80
  param_infos = param_names.zip(param_types.map{|x| type2str(x)}, param_default)
76
81
  param_infos.shift if is_hide_this_param && param_names[0] == "this"
77
82
  if param_infos.empty?
78
83
  params = ""
79
84
  else
80
- params = "\n" + param_infos.map do |pi|
81
- "\t#{pi[0].light_green} :#{pi[1]}" + (pi[2].nil? ? "" : " = #{pi[2]}".magenta)
82
- end.join(",\n") + "\n"
85
+ params = "\n" + param_infos.map.with_index do |pi, i|
86
+ "\t#{pi[0].light_green} :#{pi[1]}" + (pi[2].nil? ? "" : " = #{pi[2]}".magenta) \
87
+ + (param_infos.size == i+1 ? "" : ",") \
88
+ + xmldoc_params[pi[0]].to_s
89
+ end.join("\n") + "\n"
90
+ end
91
+ "#{namespace.cyan}#{separator.cyan}#{name.bold}(#{params}) :#{type2str(return_type).light_red} #{xmldoc_returns}" \
92
+ + (is_print_xmldoc_summary ? "\n\n#{xmldoc_summary}" : "")
93
+ end
94
+
95
+ def type2str(type)
96
+ return "nil" if type.nil?
97
+ return type.class_name if type.class == Class
98
+ rt = type.ruby_type
99
+ if type.is_a?(Types::EnumType) then "Enum" + rt.keys.to_s
100
+ elsif type.is_a?(Types::ListType) ||
101
+ type.is_a?(Types::SetType)
102
+ "#{rt.class_name}[#{type2str(type.value_type)}]"
103
+ elsif type.is_a?(Types::DictionaryType)
104
+ %Q{#{rt.class_name}[#{type2str(type.key_type)} => #{type2str(type.value_type)}]}
105
+ elsif type.is_a?(Types::TupleType)
106
+ %Q{#{rt.class_name}[#{type.value_types.map{|x| type2str(x)}.join(", ")}]}
107
+ else rt.class_name end
108
+ end
109
+
110
+ def xmlElements2str(elements, main_color, paramref_color, value_color, error_color = :red)
111
+ elements.map do |elem|
112
+ if elem.is_a?(Nokogiri::XML::Text)
113
+ elem.text.colorize(main_color)
114
+ else
115
+ begin
116
+ case elem.name
117
+ when "paramref" then elem.attr("name").underscore.colorize(paramref_color)
118
+ when "a", "math" then elem.text.colorize(main_color)
119
+ when "see"
120
+ type, _, cref = elem.attr("cref").rpartition(':')
121
+ cref = cref.split('.')
122
+ if type == 'T'
123
+ cref.join("::").colorize(value_color)
124
+ elsif type == 'M'
125
+ begin
126
+ val = cref[2].underscore.to_sym
127
+ raise RuntimeException unless ::KRPC::Gen.const_get(cref[0], false).const_get(cref[1], false)[val].is_a?(Integer)
128
+ (':' + val.to_s).colorize(value_color)
129
+ rescue
130
+ raise RuntimeException unless is_method_defined(["Services"] + cref) || is_method_defined(["Gen"] + cref)
131
+ (cref[0..-2].join("::") + "#").cyan + cref[-1].underscore
132
+ end
133
+ else raise RuntimeException end
134
+ when "list"
135
+ "!n!" + elem.children.map{|ch| "!s! * ".bold + ch.text}.join("!n!").colorize(main_color)
136
+ when "c"
137
+ elem.text.gsub("null","nil").colorize(value_color)
138
+ else elem.to_s.colorize(error_color)
139
+ end
140
+ rescue RuntimeException => exc
141
+ elem.to_s.colorize(error_color)
142
+ end
143
+ end
144
+ end.join.gsub("\n"," ").gsub("!n!","\n").squeeze(' ').strip.gsub("!s!"," ")
145
+ end
146
+
147
+ def is_method_defined(path_array)
148
+ method_name = path_array[-1].underscore
149
+ begin
150
+ cls = path_array[0..-2].inject(::KRPC){|a,e| a.const_get(e, false) }
151
+ cls.method_defined?(method_name)
152
+ rescue NameError
153
+ false
83
154
  end
84
- "#{namespace.cyan}#{separator.cyan}#{name.bold}(#{params}) :#{type2str(return_type).light_red}"
85
155
  end
86
156
 
87
157
  end
88
158
 
159
+
89
160
  module SuffixMethods
90
161
  DOCSTRING_SUFFIX = "_doc"
91
162
  DOCSTRING_SUFFIX_REGEX = /^(.+)(?:#{DOCSTRING_SUFFIX}(=)?)$/
@@ -119,4 +190,3 @@ module KRPC
119
190
 
120
191
  end
121
192
  end
122
-
@@ -9,36 +9,36 @@ module KRPC
9
9
  class << self
10
10
 
11
11
  # Given a type object, and ruby object, encode the ruby object
12
- def encode(obj, type, type_store)
12
+ def encode(obj, type)
13
13
  if type.is_a?(Types::MessageType) then obj.serialize_to_string
14
14
  elsif type.is_a?(Types::ValueType) then encode_value(obj, type)
15
15
  elsif type.is_a?(Types::EnumType)
16
16
  enum_value = type.ruby_type[obj]
17
- encode_value(enum_value, type_store.as_type("int32"))
17
+ encode_value(enum_value, TypeStore["int32"])
18
18
  elsif type.is_a?(Types::ClassType)
19
19
  remote_oid = if obj == nil then 0 else obj.remote_oid end
20
- encode_value(remote_oid, type_store.as_type("uint64"))
20
+ encode_value(remote_oid, TypeStore["uint64"])
21
21
  elsif type.is_a?(Types::ListType)
22
- msg = type_store.as_type("KRPC.List").ruby_type.new
23
- msg.items = obj.map{|x| encode(x, type.value_type, type_store)}.to_a
22
+ msg = TypeStore["KRPC.List"].ruby_type.new
23
+ msg.items = obj.map{|x| encode(x, type.value_type)}.to_a
24
24
  msg.serialize_to_string
25
25
  elsif type.is_a?(Types::DictionaryType)
26
- entry_type = type_store.as_type("KRPC.DictionaryEntry").ruby_type
27
- msg = type_store.as_type("KRPC.Dictionary").ruby_type.new
26
+ entry_type = TypeStore["KRPC.DictionaryEntry"].ruby_type
27
+ msg = TypeStore["KRPC.Dictionary"].ruby_type.new
28
28
  msg.entries = obj.map do |k,v|
29
29
  entry = entry_type.new
30
- entry.key = encode(k, type.key_type, type_store)
31
- entry.value = encode(v, type.value_type, type_store)
30
+ entry.key = encode(k, type.key_type)
31
+ entry.value = encode(v, type.value_type)
32
32
  entry
33
33
  end
34
34
  msg.serialize_to_string
35
35
  elsif type.is_a?(Types::SetType)
36
- msg = type_store.as_type("KRPC.Set").ruby_type.new
37
- msg.items = obj.map{|x| encode(x, type.value_type, type_store)}.to_a
36
+ msg = TypeStore["KRPC.Set"].ruby_type.new
37
+ msg.items = obj.map{|x| encode(x, type.value_type)}.to_a
38
38
  msg.serialize_to_string
39
39
  elsif type.is_a?(Types::TupleType)
40
- msg = type_store.as_type("KRPC.Tuple").ruby_type.new
41
- msg.items = obj.zip(type.value_types).map{|x,t| encode(x, t, type_store)}.to_a
40
+ msg = TypeStore["KRPC.Tuple"].ruby_type.new
41
+ msg.items = obj.zip(type.value_types).map{|x,t| encode(x, t)}.to_a
42
42
  msg.serialize_to_string
43
43
  else raise(RuntimeError, "Cannot encode object #{obj} of type #{type}")
44
44
  end
@@ -50,11 +50,10 @@ module KRPC
50
50
 
51
51
  def encode_request(req)
52
52
  data = req.serialize_to_string
53
- length = ProtobufUtils::Encoder.encode_nonnegative_varint(data.length)
53
+ length = ProtobufUtils::Encoder.encode_nonnegative_varint(data.length)
54
54
  length + data
55
55
  end
56
56
 
57
57
  end
58
58
  end
59
59
  end
60
-
@@ -8,10 +8,12 @@ module KRPC
8
8
 
9
9
  class ArgumentErrorSig < ArgumentError
10
10
  attr_reader :message_without_signature, :signature
11
+
11
12
  def initialize(msg = nil, sig = nil)
12
13
  @message_without_signature, @signature = msg, sig
13
14
  super(signature.nil? ? msg : msg + "\n" + signature.to_s)
14
15
  end
16
+
15
17
  def with_signature(sig)
16
18
  self.class.new(message_without_signature, sig)
17
19
  end
@@ -19,15 +21,16 @@ module KRPC
19
21
 
20
22
  class ArgumentsNumberErrorSig < ArgumentErrorSig
21
23
  attr_reader :args_count, :valid_params_count_range
24
+
22
25
  def initialize(args_count, valid_params_count_range, sig = nil)
23
26
  @args_count, @valid_params_count_range = args_count, valid_params_count_range
24
27
  valid_params_str = (valid_params_count_range.min == valid_params_count_range.max ? valid_params_count_range.min : valid_params_count_range).to_s
25
28
  super("wrong number of arguments (#{args_count} for #{valid_params_str})", sig)
26
29
  end
30
+
27
31
  def with_signature(sig)
28
32
  self.class.new(args_count, valid_params_count_range, sig)
29
33
  end
30
34
  end
31
35
 
32
36
  end
33
-
@@ -1,10 +1,12 @@
1
1
  require 'krpc/doc'
2
+ require 'krpc/streaming'
2
3
  require 'krpc/core_extensions'
4
+ require 'colorize'
3
5
 
4
6
  module KRPC
5
7
  module Gen
6
8
  AvailableToClassAndInstanceModuleName = "AvailableToClassAndInstance"
7
-
9
+
8
10
  class << self
9
11
  def service_gen_module(service_name)
10
12
  const_get_or_create(service_name, Module.new)
@@ -14,8 +16,8 @@ module KRPC
14
16
  mod = service_gen_module(service_name)
15
17
  mod.const_get_or_create(class_name) do
16
18
  Class.new(ClassBase) do
17
- attr_reader :service_name
18
19
  @service_name = service_name
20
+ class << self; attr_reader :service_name end
19
21
  end
20
22
  end
21
23
  end
@@ -27,56 +29,72 @@ module KRPC
27
29
  end
28
30
  end
29
31
 
30
- def add_rpc_method(cls, method_name, service_name, proc, client, *options)
32
+ def add_rpc_method(cls, method_name, service_name, proc, *options)
31
33
  is_static = options.include? :static
32
34
  prepend_self_to_args = options.include? :prepend_self_to_args
33
35
  target_module = is_static ? cls.const_get_or_create(AvailableToClassAndInstanceModuleName, Module.new) : cls
34
- param_names, param_types, required_params_count, param_default, return_type = parse_procedure(proc, client)
36
+ param_names, param_types, param_default, return_type = parse_procedure(proc)
35
37
  method_name = method_name.underscore
36
-
38
+
39
+ transform_exceptions = Proc.new do |method_owner, &block|
40
+ begin
41
+ block.call
42
+ rescue ArgumentsNumberErrorSig => err
43
+ sig = Doc.docstring_for_method(method_owner, method_name, false)
44
+ if prepend_self_to_args then raise ArgumentsNumberErrorSig.new(err.args_count - 1, (err.valid_params_count_range.min-1)..(err.valid_params_count_range.max-1), sig)
45
+ else raise err.with_signature(sig) end
46
+ rescue ArgumentErrorSig => err
47
+ raise err.with_signature(Doc.docstring_for_method(method_owner, method_name, false))
48
+ end
49
+ end
50
+
51
+ # Define method
37
52
  target_module.instance_eval do
38
- define_method method_name do |*args, **kwargs|
39
- begin
53
+ define_method method_name do |*args|
54
+ transform_exceptions.call(self) do
55
+ kwargs = args.extract_kwargs!
40
56
  args = [self] + args if prepend_self_to_args
41
- client.rpc(service_name, proc.name, args, kwargs, param_names, param_types, required_params_count, param_default, return_type: return_type)
42
- rescue ArgumentsNumberErrorSig => err
43
- sig = Doc.docstring_for_method(self, method_name)
44
- if prepend_self_to_args then raise ArgumentsNumberErrorSig.new(err.args_count - 1, (err.valid_params_count_range.min-1)..(err.valid_params_count_range.max-1), sig)
45
- else raise err.with_signature(sig) end
46
- rescue ArgumentErrorSig => err
47
- raise err.with_signature(Doc.docstring_for_method(self, method_name))
57
+ self.client.rpc(service_name, proc.name, args, kwargs, param_names, param_types, param_default, return_type: return_type)
48
58
  end
49
59
  end
50
60
  end
51
- Doc.add_docstring_info(is_static, cls, method_name, service_name, proc, param_names, param_types, param_default, return_type: return_type)
61
+ # Add stream-constructing Proc
62
+ unless options.include? :no_stream
63
+ cls.stream_constructors[method_name] = Proc.new do |this, *args, **kwargs|
64
+ transform_exceptions.call(this) do
65
+ req_args = prepend_self_to_args ? [this] + args : args
66
+ request = this.client.build_request(service_name, proc.name, req_args, kwargs, param_names, param_types, param_default)
67
+ this.client.streams_manager.create_stream(request, return_type, this.method(method_name), *args, **kwargs)
68
+ end
69
+ end
70
+ end
71
+ # Add docstring info
72
+ Doc.add_docstring_info(is_static, cls, method_name, service_name, proc.name, param_names, param_types, param_default, return_type: return_type, xmldoc: proc.documentation)
52
73
  end
53
74
 
54
75
  private #----------------------------------
55
76
 
56
- def parse_procedure(proc, client)
77
+ def parse_procedure(proc)
57
78
  param_names = proc.parameters.map{|p| p.name.underscore}
58
79
  param_types = proc.parameters.map.with_index do |p,i|
59
- client.type_store.get_parameter_type(i, p.type, proc.attributes)
80
+ TypeStore.get_parameter_type(i, p.type, proc.attributes)
60
81
  end
61
- param_required = proc.parameters.map{|p| not p.has_field?("default_argument")}
62
- required_params_count = param_required.take_while{|x| x}.size
63
82
  param_default = proc.parameters.zip(param_types).map do |param, type|
64
83
  if param.has_field?("default_argument")
65
- Decoder.decode(param.default_argument, type, client.type_store)
84
+ Decoder.decode(param.default_argument, type, :clientless)
66
85
  else nil
67
86
  end
68
87
  end
69
88
  return_type = if proc.has_field?("return_type")
70
- client.type_store.get_return_type(proc.return_type, proc.attributes)
71
- else nil
72
- end
73
- [param_names, param_types, required_params_count, param_default, return_type]
89
+ TypeStore.get_return_type(proc.return_type, proc.attributes)
90
+ else nil end
91
+ [param_names, param_types, param_default, return_type]
74
92
  end
75
93
  end
76
94
 
77
95
  module RPCMethodGenerator
78
- def include_rpc_method(method_name, service_name, procedure_name, return_type: nil)
79
- Gen.add_rpc_method(self.class, method_name, service_name, PB::Procedure.new(name: procedure_name, return_type: return_type), client)
96
+ def include_rpc_method(method_name, service_name, procedure_name, params: [], return_type: nil, xmldoc: "", options: [])
97
+ Gen.add_rpc_method(self.class, method_name, service_name, PB::Procedure.new(name: procedure_name, parameters: params, return_type: return_type, documentation: xmldoc), options)
80
98
  end
81
99
  end
82
100
 
@@ -94,11 +112,16 @@ module KRPC
94
112
  class ClassBase
95
113
  extend AvailableToClassAndInstanceMethodsHandler
96
114
  include Doc::SuffixMethods
115
+ include Streaming::StreamConstructors
97
116
 
98
- attr_reader :remote_oid
117
+ attr_reader :client, :remote_oid
99
118
 
100
- def initialize(remote_oid)
101
- @remote_oid = remote_oid
119
+ def self.krpc_name
120
+ name[11..-1]
121
+ end
122
+
123
+ def initialize(client, remote_oid)
124
+ @client, @remote_oid = client, remote_oid
102
125
  end
103
126
 
104
127
  alias_method :eql?, :==
@@ -109,11 +132,14 @@ module KRPC
109
132
  remote_oid.hash
110
133
  end
111
134
 
112
- def self.krpc_name
113
- name[11..-1]
135
+ def to_s
136
+ "#<#{self.class} @remote_oid=#{remote_oid}>"
137
+ end
138
+
139
+ def inspect
140
+ "#<#{self.class} ".green + "@remote_oid" + "=".green + remote_oid.to_s.bold.blue + ">".green
114
141
  end
115
142
  end
116
143
 
117
144
  end
118
145
  end
119
-
@@ -4,11 +4,11 @@ module KRPC
4
4
  module ProtobufUtils
5
5
  class << self
6
6
  def create_PB_to_PB_message_class_hash(package)
7
- protobuf_module = Kernel.const_get(package.gsub(".","::") + "::PB")
8
- msg_classes_names = protobuf_module.constants.select{|c| protobuf_module.const_get(c,false).superclass == ::Protobuf::Message}
9
- msg_classes_names.map do |name|
10
- [package + "." + name.to_s, protobuf_module.const_get(name,false)]
11
- end.to_h
7
+ protobuf_module = Kernel.const_get(package.gsub(".","::") + "::PB")
8
+ msg_classes_names = protobuf_module.constants.select{|c| protobuf_module.const_get(c,false).superclass == ::Protobuf::Message}
9
+ msg_classes_names.map do |name|
10
+ [package + "." + name.to_s, protobuf_module.const_get(name,false)]
11
+ end.to_h
12
12
  end
13
13
  end
14
14
 
@@ -29,7 +29,7 @@ module KRPC
29
29
  result = 0
30
30
  shift = 0
31
31
  loop do
32
- byte = bytes[pos].ord # <-- replace ".ord" with ".bytes[0]" if ".ord" start to cause problems again
32
+ byte = bytes[pos].ord
33
33
  pos += 1
34
34
  result |= (byte & 0b0111_1111) << shift
35
35
  return [result, pos] if (byte & 0b1000_0000) == 0
@@ -129,5 +129,3 @@ module KRPC
129
129
 
130
130
  end
131
131
  end
132
-
133
-