motion_blender 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e0a26f4930010d2e1105308d10fb471cc3e3892
4
- data.tar.gz: f537cd46e5096820fc007f634aa127384a7d9bfe
3
+ metadata.gz: 96d8bca0eabd7579ef143fb1d513f19f3107b96d
4
+ data.tar.gz: 0400cc6dc5ed4eddf57120ddcdc4a444ac0ce710
5
5
  SHA512:
6
- metadata.gz: bafb18c7858a0d59a7c341ebc1ed6ae809622b3b86c99d9435e0992cc36cead2e819251249798fbca5db20b162363bb01c45d16332fe150c9cdf463d76fb6a0f
7
- data.tar.gz: 9948b5186e33231b7d286d6b6b9cd013a3ae3c7a008e6954c4cc4ab13a70bb15a5475d14c64bf572088c416524fe0a3fd0ddd8c55c617b42290d468940e2841f
6
+ metadata.gz: 48421b7fba3cb60c261a3958d60c439b0b43fe9bfadc3fb6f297b567ef647d56cb549de3fa3c367f3436034f068ec1c099cad027c4455cd55296aacf73a88463
7
+ data.tar.gz: b5f0a8a4a50c58bcea1e487d4b4d40d5e4999cc4d57e48a95f760f12ddfe8006d7375900938d66d489c49396fccde30c36f492a71fdd0bb5bf82d6648e96c14f
@@ -19,6 +19,11 @@ Metrics/ModuleLength:
19
19
  Metrics/AbcSize:
20
20
  Max: 25
21
21
 
22
+ # Cop supports --auto-correct.
23
+ Performance/RedundantBlockCall:
24
+ Exclude:
25
+ - 'motion/**/*'
26
+
22
27
  # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
23
28
  Style/BlockDelimiters:
24
29
  Exclude:
@@ -73,7 +78,7 @@ Style/MultilineBlockLayout:
73
78
  - 'spec/**/*'
74
79
 
75
80
  # Cop supports --auto-correct.
76
- Style/SingleSpaceBeforeFirstArg:
81
+ Style/SpaceBeforeFirstArg:
77
82
  Enabled: false
78
83
 
79
84
  # Cop supports --auto-correct.
@@ -1,8 +1,10 @@
1
- os: osx
2
1
  language: ruby
3
- rvm:
4
- - 2.2.3
2
+ matrix:
3
+ include:
4
+ - rvm: 2.2.5
5
+ os: osx
6
+ osx_image: xcode7.3 # OSX 10.11
5
7
  before_install:
6
- - gem install bundler -v 1.10.6
8
+ - gem install bundler
7
9
  - brew update
8
10
  - brew install graphviz --with-gts
@@ -11,24 +11,48 @@ module MotionBlender
11
11
  @analyzed_files = Set.new
12
12
  @files = []
13
13
  @dependencies = {}
14
+ @autoloads = {}
15
+ @file_stack = []
14
16
  end
15
17
 
16
18
  def analyze file, backtrace = []
19
+ @file_stack.push file
20
+
17
21
  return if MotionBlender.config.excepted_files.include? file
18
22
  return if @analyzed_files.include? file
19
23
  @analyzed_files << file
20
24
 
21
- requires = parse file, backtrace
22
-
23
- if requires.present?
24
- @dependencies[file] = requires.map(&:file)
25
- @files = [*@files, file, *@dependencies[file]].uniq
26
- requires.each do |req|
27
- req.run_callbacks :require do
28
- analyze req.file, [req.trace, *backtrace]
29
- end
25
+ parser = parse file, backtrace
26
+ requires = merge parser
27
+ requires.each do |req|
28
+ req.run_callbacks :require do
29
+ analyze req.file, [req.trace, *backtrace]
30
30
  end
31
31
  end
32
+ ensure
33
+ @file_stack.pop
34
+ end
35
+
36
+ def pick_autoloads parser
37
+ parser.requires.select(&:autoload?).each do |req|
38
+ @autoloads[req.autoload_const_name] ||= []
39
+ @autoloads[req.autoload_const_name] << req
40
+ end
41
+ end
42
+
43
+ def merge parser
44
+ pick_autoloads parser
45
+
46
+ reqs = parser.dependent_requires(autoloads: @autoloads)
47
+ reqs = reqs.reject { |req| @file_stack.include? req.file }
48
+ if reqs.present?
49
+ files = reqs.map(&:file)
50
+ @dependencies[parser.file] = files
51
+ @files = [*@files, parser.file, *files].uniq
52
+ reqs
53
+ else
54
+ []
55
+ end
32
56
  end
33
57
 
34
58
  def parse file, backtrace
@@ -39,7 +63,7 @@ module MotionBlender
39
63
  err.set_backtrace [parser.last_trace, *backtrace].compact
40
64
  raise err
41
65
  end
42
- parser.requires
66
+ parser
43
67
  end
44
68
  end
45
69
  end
@@ -2,7 +2,7 @@ module MotionBlender
2
2
  class Analyzer
3
3
  class Cache
4
4
  attr_reader :file, :hit
5
- alias_method :hit?, :hit
5
+ alias hit? hit
6
6
 
7
7
  def initialize file
8
8
  @file = Pathname.new(file)
@@ -6,11 +6,13 @@ module MotionBlender
6
6
  attr_reader :source
7
7
  attr_reader :trace, :requires
8
8
  attr_reader :dynamic
9
- alias_method :dynamic?, :dynamic
9
+ alias dynamic? dynamic
10
+ attr_reader :done
11
+ alias done? done
10
12
 
11
13
  def initialize source
12
14
  @source = source
13
- @trace = "#{source.file}:#{source.line}:in `#{source.method}'"
15
+ @trace = source.to_s
14
16
  @requires = []
15
17
  @dynamic = false
16
18
  end
@@ -19,19 +21,22 @@ module MotionBlender
19
21
  return if @source.evaluated?
20
22
  @source.evaluated!
21
23
 
22
- @requires = Collector.collect_requires(@source)
24
+ @requires = Collector.new(@source).collect_requires
23
25
  @requires.each do |req|
24
26
  req.trace = @trace
25
27
  end
28
+ @done = true
26
29
  self
27
30
  rescue StandardError, ScriptError => err
28
31
  recover_from_error err
29
32
  end
30
33
 
34
+ private
35
+
31
36
  def recover_from_error err
32
37
  @source = @source.parent
33
38
  @source = @source.parent if @source && @source.type.rescue?
34
- fail LoadError, err.message unless @source
39
+ raise LoadError, err.message unless @source
35
40
  @dynamic = true
36
41
  run
37
42
  end
@@ -10,7 +10,7 @@ module MotionBlender
10
10
  include ActiveSupport::Callbacks
11
11
  define_callbacks :parse
12
12
 
13
- attr_reader :file, :evaluators, :cache
13
+ attr_reader :file, :evaluators, :cache, :referring_constants
14
14
 
15
15
  def initialize file
16
16
  @file = file.to_s
@@ -18,17 +18,22 @@ module MotionBlender
18
18
  end
19
19
 
20
20
  def parse
21
- srcs = cache.fetch do
21
+ cached = cache.fetch do
22
22
  run_callbacks :parse do
23
- traverse(Source.parse_file(@file))
24
- @evaluators.map(&:source).map(&:attributes)
23
+ root = Source.parse_file(@file)
24
+ traverse(root)
25
+ {
26
+ sources: processed_sources.map(&:attributes),
27
+ referring_constants: root.referring_constants
28
+ }
25
29
  end
26
30
  end
27
- if srcs && cache.hit?
28
- srcs.each do |attrs|
31
+ if cached && cache.hit?
32
+ cached[:sources].each do |attrs|
29
33
  evaluate Source.new(attrs)
30
34
  end
31
35
  end
36
+ @referring_constants = cached[:referring_constants]
32
37
  self
33
38
  end
34
39
 
@@ -40,7 +45,7 @@ module MotionBlender
40
45
  if Collector.requirable?(source)
41
46
  evaluate source
42
47
  elsif Collector.acceptable?(source)
43
- source.children.each { |src| traverse src }
48
+ source.children.compact.each { |src| traverse src }
44
49
  end
45
50
  end
46
51
 
@@ -53,9 +58,31 @@ module MotionBlender
53
58
  @evaluators.map(&:requires).flatten
54
59
  end
55
60
 
61
+ def processed_sources
62
+ @evaluators.select(&:done?).map(&:source)
63
+ end
64
+
56
65
  def last_trace
57
66
  @evaluators.last.try :trace
58
67
  end
68
+
69
+ def autoloads_with autoloads
70
+ referring_constants.map do |mods, const|
71
+ key =
72
+ mods
73
+ .length.downto(0)
74
+ .map { |i| [*mods.take(i), const].join('::') }
75
+ .find { |k| autoloads.key?(k) }
76
+ autoloads[key]
77
+ end.flatten.compact
78
+ end
79
+
80
+ def dependent_requires opts = {}
81
+ autoloads = opts[:autoloads]
82
+ reqs = requires.reject(&:autoload?)
83
+ reqs += autoloads_with(autoloads) if autoloads
84
+ reqs.uniq(&:file)
85
+ end
59
86
  end
60
87
  end
61
88
  end
@@ -22,55 +22,81 @@ module MotionBlender
22
22
  (source.children.first.code != 'MotionBlender.raketime')
23
23
  end
24
24
 
25
- def collect_requires source
26
- collector = new(source, interpreters)
27
- interpreters.each do |interpreter|
28
- get_refinement_for(interpreter.receiver).module_eval do
29
- define_method interpreter.method do |*args, &proc|
30
- collector.interpreters[interpreter.key].interpret(*args, &proc)
31
- end
32
- end
25
+ def refinements
26
+ @refinements ||= Hash.new do |hash, key|
27
+ hash[key] = Module.new { key.prepend self }
33
28
  end
34
- Object.new.instance_eval(source.code, source.file, source.line)
35
- collector.requires
36
- ensure
37
- clear_refinements
38
29
  end
30
+ end
39
31
 
40
- private
32
+ attr_accessor :source, :interpreters, :requires
41
33
 
42
- def refinements
43
- @refinements ||= {}
34
+ def initialize source
35
+ @source = source
36
+ @interpreters = self.class.interpreters.map do |interpreter|
37
+ [interpreter.key, interpreter.new(self)]
38
+ end.to_h
39
+ @requires = []
40
+ end
41
+
42
+ def collect_requires
43
+ obj = evaluating_object
44
+ if obj.is_a? Module
45
+ with_refinements do
46
+ obj.module_eval(source.code, source.file, source.line)
47
+ end
48
+ else
49
+ with_refinements do
50
+ obj.instance_eval(source.code, source.file, source.line)
51
+ end
44
52
  end
53
+ requires
54
+ end
45
55
 
46
- def get_refinement_for klass
47
- refinements[klass] ||=
48
- begin
49
- Module.new do
50
- klass.prepend self
51
- end
52
- end
56
+ private
57
+
58
+ delegate :refinements, to: :class
59
+
60
+ def evaluating_object
61
+ obj = Object.new
62
+ constants = @source.global_constants.select do |c|
63
+ Object.const_defined? c.to_sym
53
64
  end
65
+ constants.each { |c| obj.instance_eval "#{c} = ::#{c}" }
66
+ if @source.wrapping_modules.present?
67
+ ms = @source.wrapping_modules.map { |p| p.join(' ') }
68
+ s = [*ms, 'self', *Array.new(ms.length, 'end')].join(';')
69
+ obj = obj.instance_eval s
70
+ end
71
+ obj
72
+ end
54
73
 
55
- def clear_refinements
56
- refinements.each do |_, mod|
57
- mod.module_eval do
58
- instance_methods.each do |m|
59
- remove_method m
60
- end
74
+ def with_refinements
75
+ apply_refinements
76
+ yield
77
+ ensure
78
+ clear_refinements
79
+ end
80
+
81
+ def apply_refinements
82
+ interpreters.each do |_, interpreter|
83
+ refinements[interpreter.receiver].module_eval do
84
+ define_method interpreter.method do |*args, &proc|
85
+ interpreter.object = self
86
+ interpreter.interpret(*args, &proc)
61
87
  end
62
88
  end
63
89
  end
64
90
  end
65
91
 
66
- attr_accessor :source, :interpreters, :requires
67
-
68
- def initialize source, interpreters
69
- @source = source
70
- @interpreters = interpreters.map do |interpreter|
71
- [interpreter.key, interpreter.new(self)]
72
- end.to_h
73
- @requires = []
92
+ def clear_refinements
93
+ refinements.each do |_, mod|
94
+ mod.module_eval do
95
+ instance_methods.each do |m|
96
+ remove_method m
97
+ end
98
+ end
99
+ end
74
100
  end
75
101
  end
76
102
  end
@@ -4,7 +4,7 @@ module MotionBlender
4
4
  class DependencyGraph < Hash
5
5
  include TSort
6
6
 
7
- alias_method :tsort_each_node, :each_key
7
+ alias tsort_each_node each_key
8
8
 
9
9
  def tsort_each_child node, &block
10
10
  (self[node] || []).each(&block)
@@ -0,0 +1,23 @@
1
+ require 'motion_blender/interpreters/require_interpreter'
2
+
3
+ module MotionBlender
4
+ module Interpreters
5
+ class AutoloadInterpreter < RequireInterpreter
6
+ interprets :autoload
7
+
8
+ def interpret const_name, arg
9
+ req = super arg
10
+ req.autoload_const_name =
11
+ if object.is_a? Module
12
+ [*object.name.sub(/^#<.+?>::/, ''), const_name].join('::')
13
+ else
14
+ const_name.to_s
15
+ end
16
+ end
17
+ end
18
+
19
+ class ModuleAutoloadInterpreter < AutoloadInterpreter
20
+ interprets :autoload, receiver: Module
21
+ end
22
+ end
23
+ end
@@ -22,6 +22,7 @@ module MotionBlender
22
22
  end
23
23
 
24
24
  attr_reader :collector
25
+ attr_accessor :object
25
26
  delegate :method, :receiver, to: :class
26
27
  delegate :source, :requires, to: :collector
27
28
  delegate :file, to: :source
@@ -9,7 +9,7 @@ module MotionBlender
9
9
 
10
10
  def interpret
11
11
  dir = MotionBlender.config.motion_dirs.find { |d| file.start_with? d }
12
- fail 'not found in motion_dirs' unless dir
12
+ raise 'not found in motion_dirs' unless dir
13
13
  arg = Pathname.new(file).relative_path_from(Pathname.new(dir))
14
14
  @original = candidates_for(arg).find(&:file?).try(&:to_s)
15
15
  end
@@ -16,13 +16,12 @@ module MotionBlender
16
16
  req.file = resolve_path req.arg
17
17
  return if excluded_file? req.file
18
18
 
19
- yield req
20
- true
19
+ req
21
20
  end
22
21
 
23
22
  def resolve_path arg
24
23
  path = candidates(arg).find(&:file?)
25
- fail LoadError, "not found `#{arg}'" unless path
24
+ raise LoadError, "not found `#{arg}'" unless path
26
25
  explicit_relative path
27
26
  end
28
27
 
@@ -8,8 +8,10 @@ module MotionBlender
8
8
  interprets :require
9
9
 
10
10
  def interpret arg
11
- find_require(arg) do |req|
11
+ req = find_require(arg)
12
+ if req
12
13
  requires << req
14
+ req
13
15
  end
14
16
  end
15
17
 
@@ -1,17 +1,10 @@
1
- require 'motion_blender/interpreters/base'
1
+ require 'motion_blender/interpreters/require_interpreter'
2
2
 
3
3
  module MotionBlender
4
4
  module Interpreters
5
- class RequireRelativeInterpreter < Base
6
- include Requirable
5
+ class RequireRelativeInterpreter < RequireInterpreter
7
6
  interprets :require_relative
8
7
 
9
- def interpret arg
10
- find_require(arg) do |req|
11
- requires << req
12
- end
13
- end
14
-
15
8
  def candidates arg
16
9
  path = Pathname.new(file).dirname.join(arg)
17
10
  exts = path.extname.empty? ? ['', '.rb'] : ['']
@@ -7,7 +7,7 @@ module MotionBlender
7
7
  define_callbacks :require
8
8
 
9
9
  attr_reader :loader, :method, :arg
10
- attr_accessor :trace, :file
10
+ attr_accessor :trace, :file, :autoload_const_name
11
11
 
12
12
  def initialize loader, method, arg
13
13
  @loader = loader
@@ -18,5 +18,9 @@ module MotionBlender
18
18
  def match? arg_or_file
19
19
  [arg, file].compact.include?(arg_or_file)
20
20
  end
21
+
22
+ def autoload?
23
+ method == :autoload
24
+ end
21
25
  end
22
26
  end
@@ -1,8 +1,16 @@
1
1
  require 'parser/current'
2
2
  require 'motion_blender/flag_attribute'
3
+ require 'motion_blender/source/wrapping_modules'
4
+ require 'motion_blender/source/global_constants'
5
+ require 'motion_blender/source/referring_constants'
3
6
 
4
7
  module MotionBlender
5
8
  class Source
9
+ include FlagAttribute
10
+ include WrappingModules
11
+ include GlobalConstants
12
+ include ReferringConstants
13
+
6
14
  def self.parse code, attrs = {}
7
15
  attrs[:ast] = ::Parser::CurrentRuby.parse(code)
8
16
  new(attrs)
@@ -13,8 +21,6 @@ module MotionBlender
13
21
  new(ast: ast)
14
22
  end
15
23
 
16
- include FlagAttribute
17
-
18
24
  attr_reader :code, :file, :line, :parent, :type, :method, :ast
19
25
  flag_attribute :evaluated
20
26
 
@@ -42,13 +48,31 @@ module MotionBlender
42
48
 
43
49
  def children
44
50
  @children ||=
45
- if @ast
46
- @ast.children.grep(::Parser::AST::Node).map do |ast|
47
- Source.new(ast: ast, parent: self)
48
- end
49
- else
50
- []
51
- end
51
+ @ast
52
+ .try(:children).to_a
53
+ .select { |ast| ast.nil? || ast.is_a?(::Parser::AST::Node) }
54
+ .map { |ast| Source.new(ast: ast, parent: self) }
55
+ end
56
+
57
+ def child_at *args
58
+ i = args.shift
59
+ args.present? ? children[i].child_at(*args) : children[i]
60
+ end
61
+
62
+ def root?
63
+ parent.nil?
64
+ end
65
+
66
+ def root
67
+ root? ? self : parent.root
68
+ end
69
+
70
+ def ancestors
71
+ root? ? [self] : [self, *parent.ancestors]
72
+ end
73
+
74
+ def like_module?
75
+ type.module? || type.class?
52
76
  end
53
77
 
54
78
  def attributes
@@ -57,7 +81,9 @@ module MotionBlender
57
81
  'file' => @file,
58
82
  'line' => @line,
59
83
  'type' => @type.to_s,
60
- 'method' => @method.try(:to_s)
84
+ 'method' => @method.try(:to_s),
85
+ 'global_constants' => global_constants,
86
+ 'wrapping_modules' => wrapping_modules
61
87
  }
62
88
  end
63
89
  end
@@ -0,0 +1,24 @@
1
+ module MotionBlender
2
+ class Source
3
+ module GlobalConstants
4
+ def global_constants
5
+ @global_constants ||=
6
+ if root?
7
+ Array.wrap(find_global_constants).flatten.compact.uniq
8
+ else
9
+ root.global_constants
10
+ end
11
+ end
12
+
13
+ def find_global_constants
14
+ if like_module?
15
+ if children.first.type.const?
16
+ children.first.code.split('::', 2).first
17
+ end
18
+ else
19
+ children.compact.map(&:find_global_constants)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module MotionBlender
2
+ class Source
3
+ module ReferringConstants
4
+ def referring_constants
5
+ @referring_constants ||=
6
+ begin
7
+ child_constants =
8
+ children.map(&:referring_constants).inject(&:+).to_a
9
+ if referring_constant?
10
+ child_constants + [[wrapping_modules.map(&:last), code]]
11
+ else
12
+ child_constants.dup
13
+ end
14
+ end
15
+ end
16
+
17
+ def referring_constant?
18
+ type.const?
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module MotionBlender
2
+ class Source
3
+ module WrappingModules
4
+ def wrapping_modules
5
+ @wrapping_modules ||=
6
+ [*parent.try(:wrapping_modules), parent_module].compact
7
+ end
8
+
9
+ def parent_module
10
+ if module_content? || class_content?
11
+ [parent.type.to_s, parent.children.first.code]
12
+ end
13
+ end
14
+
15
+ def module_content?
16
+ parent && parent.type.module? && (parent.children[1] == self)
17
+ end
18
+
19
+ def class_content?
20
+ parent && parent.type.class? && (parent.children[2] == self)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module MotionBlender
2
- VERSION = '0.3.1'
2
+ VERSION = '0.4.0'.freeze
3
3
  end
@@ -5,6 +5,14 @@ class Object
5
5
  def require_relative *_
6
6
  end
7
7
 
8
+ def autoload *_
9
+ end
10
+
8
11
  def __ORIGINAL__ *_
9
12
  end
10
13
  end
14
+
15
+ class Module
16
+ def autoload *_
17
+ end
18
+ end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_runtime_dependency 'parser'
22
+ spec.add_runtime_dependency 'parser', '~> 2.3.1'
23
23
  spec.add_runtime_dependency 'activesupport', '~> 4.2'
24
24
  spec.add_runtime_dependency 'colorable', '~> 0.2'
25
25
  spec.add_runtime_dependency 'ruby-graphviz', '~> 1.2'
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion_blender
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kayhide
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-09 00:00:00.000000000 Z
11
+ date: 2016-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 2.3.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 2.3.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -268,6 +268,7 @@ files:
268
268
  - lib/motion_blender/flag_attribute.rb
269
269
  - lib/motion_blender/graph_maker.rb
270
270
  - lib/motion_blender/interpreters.rb
271
+ - lib/motion_blender/interpreters/autoload_interpreter.rb
271
272
  - lib/motion_blender/interpreters/base.rb
272
273
  - lib/motion_blender/interpreters/original_interpreter.rb
273
274
  - lib/motion_blender/interpreters/requirable.rb
@@ -276,6 +277,9 @@ files:
276
277
  - lib/motion_blender/rake_tasks.rb
277
278
  - lib/motion_blender/require.rb
278
279
  - lib/motion_blender/source.rb
280
+ - lib/motion_blender/source/global_constants.rb
281
+ - lib/motion_blender/source/referring_constants.rb
282
+ - lib/motion_blender/source/wrapping_modules.rb
279
283
  - lib/motion_blender/version.rb
280
284
  - motion/motion_blender/ext.rb
281
285
  - motion/motion_blender/ext/runtime.rb