rinline 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dc0964e6130a5e6bd8f80a5ada38a861082d5576b012c5557e90c07646942d76
4
+ data.tar.gz: c3a30e6a0c4c5de9c4e003abc2ace61c17a92cb375296de967ffff58e1acb308
5
+ SHA512:
6
+ metadata.gz: 115a1a1f849277b1725e6c32555e9a85acc39a18dbcc5d7738a0825c3df0921f788dea8b1e34ccf3337d1add62aafe483c5178bd8c07ad1a4ad794b9b1809864
7
+ data.tar.gz: 5d00f2b9fccf4811176f91a4e3e96c7a966041c4bf0b4c2d719ee451944d0aa47c7525d035e3b116bd70509e99a3503f21c9cfc56e9b1c7f0a6b40b3cb12ba13
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rinline.gemspec
4
+ gemspec
@@ -0,0 +1,44 @@
1
+ # Rinline
2
+
3
+ Inline expansion for Ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rinline'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rinline
20
+
21
+ ## Usage
22
+
23
+ Call `Rinline.optimize` after your program is loaded, but before your program is not ran yet.
24
+
25
+ ```ruby
26
+ require 'your_program'
27
+ require 'rinline'
28
+
29
+ Rinline.optimize do |r|
30
+ r.optimize_namespace(YourProgram)
31
+ end
32
+
33
+ YourProgram.start
34
+ ```
35
+
36
+ ## Development
37
+
38
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
39
+
40
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pocke/rinline.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |test|
6
+ test.libs << 'test'
7
+ test.test_files = Dir['test/**/*_test.rb']
8
+ test.verbose = true
9
+ end
@@ -0,0 +1,36 @@
1
+ # $ ruby benchmark/simple.rb
2
+ # user system total real
3
+ # plain 6.203292 0.000000 6.203292 ( 6.206694)
4
+ # optimized 3.623372 0.000000 3.623372 ( 3.625261)
5
+
6
+ require 'benchmark'
7
+
8
+ class C
9
+ def plain
10
+ m + n
11
+ end
12
+
13
+ def optimized
14
+ m + n
15
+ end
16
+
17
+ def m
18
+ 1
19
+ end
20
+
21
+ def n
22
+ 2
23
+ end
24
+ end
25
+
26
+ require 'rinline'
27
+ Rinline.optimize do |r|
28
+ r.optimize_instance_method(C, :optimized)
29
+ end
30
+
31
+ i = C.new
32
+
33
+ Benchmark.bm(20) do |x|
34
+ x.report('plain') { 100000000.times { i.plain } }
35
+ x.report('optimized') { 100000000.times { i.optimized } }
36
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rinline"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,22 @@
1
+ require 'securerandom'
2
+
3
+ require_relative "./rinline/version"
4
+ require_relative './rinline/ext/iseq_ext'
5
+ require_relative './rinline/ext/ast_ext'
6
+ require_relative './rinline/ext/method_ext'
7
+ require_relative './rinline/optimizer'
8
+ require_relative './rinline/location'
9
+ require_relative './rinline/runner'
10
+
11
+ module Rinline
12
+ extend self
13
+
14
+ def optimize(&block)
15
+ runner = Runner.new
16
+ Runner.current = runner
17
+ block.call runner
18
+ Runner.current = nil
19
+
20
+ $stderr.puts "[Rinline] Optimizing is finised" if runner.debug
21
+ end
22
+ end
@@ -0,0 +1,181 @@
1
+ module Rinline
2
+ module Ext
3
+ module AstExt
4
+ refine RubyVM::AbstractSyntaxTree::Node do
5
+ def traverse(&block)
6
+ opt = {}
7
+ block.call self, opt
8
+
9
+ # workaround for modifiers
10
+ # TODO: Remove this workaround by refining offset
11
+ if ((type == :IF || type == :UNLESS) && children[2] == nil && children[1].before_than(children[0])) ||
12
+ ((type == :WHILE || type == :UNTIL) && children[1].before_than(children[0]))
13
+ children[1].traverse(&block)
14
+ children[0].traverse(&block)
15
+ else
16
+ self.children.each.with_index do |child, index|
17
+ next if opt[:ignore_index] == index
18
+ child.traverse(&block) if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
19
+ end
20
+ end
21
+ end
22
+
23
+ def to_source(path)
24
+ file(path)[first_index(path)..last_index(path)]
25
+ end
26
+
27
+ def location(path)
28
+ Location.new(first_index(path), last_index(path))
29
+ end
30
+
31
+ def has_child?(*types)
32
+ traverse do |child|
33
+ return true if child.is_a?(RubyVM::AbstractSyntaxTree::Node) && types.include?(child.type)
34
+ end
35
+ return false
36
+ end
37
+
38
+ # var = 1
39
+ # ^^^
40
+ def location_variable_name_of_lasgn(path)
41
+ type! :LASGN
42
+ first_index = first_index(path)
43
+
44
+ Location.new(first_index, first_index + self.children[0].size - 1)
45
+ end
46
+
47
+ def method_body
48
+ type! :SCOPE
49
+ self.children[2]
50
+ end
51
+
52
+ def method_args
53
+ type! :SCOPE
54
+ self.children[1]
55
+ end
56
+
57
+ # Extensions for ARGS
58
+ def args_pre_num
59
+ type! :ARGS
60
+ self.children[0]
61
+ end
62
+
63
+ def args_pre_init
64
+ type! :ARGS
65
+ self.children[1]
66
+ end
67
+
68
+ def args_opt
69
+ type! :ARGS
70
+ self.children[2]
71
+ end
72
+
73
+ def args_first_post
74
+ type! :ARGS
75
+ self.children[3]
76
+ end
77
+
78
+ def args_post_num
79
+ type! :ARGS
80
+ self.children[4]
81
+ end
82
+
83
+ def args_post_init
84
+ type! :ARGS
85
+ self.children[5]
86
+ end
87
+
88
+ def args_rest
89
+ type! :ARGS
90
+ self.children[6]
91
+ end
92
+
93
+ def args_kw
94
+ type! :ARGS
95
+ self.children[7]
96
+ end
97
+
98
+ def args_kwrest
99
+ type! :ARGS
100
+ self.children[8]
101
+ end
102
+
103
+ def args_block
104
+ type! :ARGS
105
+ self.children[9]
106
+ end
107
+
108
+ def expandable_method?(parameter_size)
109
+ a = self.method_args
110
+ a.args_pre_num == parameter_size &&
111
+ a.args_pre_init == nil &&
112
+ a.args_opt == nil &&
113
+ a.args_first_post == nil &&
114
+ a.args_post_num == 0 &&
115
+ a.args_post_init == nil &&
116
+ a.args_rest == nil &&
117
+ a.args_kw == nil &&
118
+ a.args_kwrest == nil &&
119
+ a.args_block == nil
120
+ end
121
+
122
+ def array_size
123
+ type! :ARRAY
124
+ self.children.size - 1
125
+ end
126
+
127
+ def array_content
128
+ type! :ARRAY
129
+ self.children[0..-2]
130
+ end
131
+
132
+ def fcall_args
133
+ type! :FCALL
134
+ self.children[1]
135
+ end
136
+
137
+ def lasgn_opasgn?
138
+ type! :LASGN
139
+ right = children[1]
140
+ first_lineno == right.first_lineno &&
141
+ first_column == right.first_column
142
+ end
143
+
144
+ private def first_index(path)
145
+ return first_column if first_lineno == 1
146
+
147
+ lines = file(path).split("\n")
148
+ lines[0..(first_lineno - 2)].sum(&:size) +
149
+ first_lineno - 1 + # For \n
150
+ first_column
151
+ end
152
+
153
+ private def last_index(path)
154
+ last_column = self.last_column - 1
155
+ return last_column if last_lineno == 1
156
+
157
+ lines = file(path).split("\n")
158
+ lines[0..(last_lineno - 2)].sum(&:size) +
159
+ last_lineno - 1 + # For \n
160
+ last_column
161
+ end
162
+
163
+ private def type!(type)
164
+ raise "Unexpected type: #{self.type}" unless self.type == type
165
+ end
166
+
167
+ def before_than(right)
168
+ if self.first_lineno == right.first_lineno
169
+ self.first_column < right.first_column
170
+ else
171
+ self.first_lineno < right.first_lineno
172
+ end
173
+ end
174
+
175
+ private def file(path)
176
+ Runner.current.file_cache[path] ||= File.binread(path)
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,11 @@
1
+ module Rinline
2
+ module Ext
3
+ module IseqExt
4
+ refine RubyVM::InstructionSequence do
5
+ def short?
6
+ self.to_a[13].size < Runner.current.iseq_threshold
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ module Rinline
2
+ module Ext
3
+ module MethodExt
4
+ refine UnboundMethod do
5
+ using IseqExt
6
+ using AstExt
7
+
8
+ def to_ast
9
+ RubyVM::AbstractSyntaxTree.of(self)
10
+ end
11
+
12
+ def to_iseq
13
+ RubyVM::InstructionSequence.of(self)
14
+ end
15
+
16
+ def expandable?
17
+ ruby_method? &&
18
+ to_iseq&.short? &&
19
+ !eval? &&
20
+ !to_ast.has_child?(:SUPER, :ZSUPER, :RETURN, :CONST) &&
21
+ # HACK: RubyVM::AST omits `return` from tree if it is meaningless.
22
+ # So checking AST is not enough.
23
+ !to_ast.to_source(absolute_path).match?(/\breturn\b/)
24
+ end
25
+
26
+ def ruby_method?
27
+ !!source_location
28
+ end
29
+
30
+ def eval?
31
+ absolute_path == '(eval)'
32
+ end
33
+
34
+ def absolute_path
35
+ source_location[0]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ module Rinline
2
+ class Location
3
+ def initialize(first_index, last_index)
4
+ @first_index = first_index
5
+ @last_index = last_index
6
+ end
7
+
8
+ attr_reader :first_index, :last_index
9
+
10
+ def size
11
+ last_index - first_index + 1
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,155 @@
1
+ module Rinline
2
+ class Optimizer
3
+ using Ext::MethodExt
4
+ using Ext::AstExt
5
+ using Ext::IseqExt
6
+
7
+ # @param klass [Class]
8
+ # @param method_name [Symbol] an instance method name
9
+ # @return [String,nil] optimized code if optimized
10
+ def self.optimize(klass, method_name)
11
+ self.new(klass, method_name).optimize
12
+ end
13
+
14
+ def initialize(klass, method_name)
15
+ @klass = klass
16
+ @method_name = method_name
17
+ @method = klass.instance_method(method_name)
18
+ end
19
+
20
+ def optimize
21
+ return unless method.ruby_method?
22
+ return if method.eval?
23
+ ast = method.to_ast
24
+ return unless ast
25
+ return if ast.has_child?(:CONST)
26
+ path = method.absolute_path
27
+ replacements = []
28
+
29
+ ast.traverse do |node, opt|
30
+ case node.type
31
+ when :VCALL
32
+ target_method_name = node.children[0]
33
+ next if method_name == target_method_name
34
+ target_method =
35
+ begin
36
+ klass.instance_method(target_method_name)
37
+ rescue NameError
38
+ next
39
+ end
40
+ next unless target_method.expandable?
41
+
42
+ to_ast = target_method.to_ast
43
+ next unless to_ast.expandable_method?(0)
44
+
45
+ to_path = target_method.absolute_path
46
+ body = to_ast.method_body
47
+ to_code =
48
+ if body
49
+ "((#{replace_lvar(body, to_path)}))"
50
+ else
51
+ "()"
52
+ end
53
+ replacements << {
54
+ from: node.location(path),
55
+ to: to_code,
56
+ }
57
+ when :FCALL
58
+ target_method_name = node.children[0]
59
+ next if method_name == target_method_name
60
+ target_method =
61
+ begin
62
+ klass.instance_method(target_method_name)
63
+ rescue NameError
64
+ next
65
+ end
66
+ next unless target_method.expandable?
67
+
68
+ to_ast = target_method.to_ast
69
+ args = node.fcall_args
70
+ next unless args&.type == :ARRAY
71
+ next unless to_ast.expandable_method?(args.array_size)
72
+
73
+ to_path = target_method.absolute_path
74
+ lvar_suffix = gen_lvar_suffix
75
+
76
+ args = assign_args(to_ast, node, path, lvar_suffix)
77
+ body =
78
+ if to_ast.method_body
79
+ replace_lvar(to_ast.method_body, to_path, lvar_suffix: lvar_suffix)
80
+ else
81
+ "()"
82
+ end
83
+ to_code = "((#{args}#{body}))"
84
+ replacements << {
85
+ from: node.location(path),
86
+ to: to_code
87
+ }
88
+ opt[:ignore_index] = 1 # Ignore arguments
89
+ end
90
+ end
91
+
92
+ return if replacements.empty?
93
+ return replace(ast, path, replacements).force_encoding(Encoding::UTF_8) # TODO: Support other encodings
94
+ end
95
+
96
+ attr_reader :klass, :method_name, :method
97
+ private :klass, :method_name, :method
98
+
99
+ # @param original_method [Method]
100
+ # @param replacements [Array<{from: Rinline::Location, to: String}>]
101
+ private def replace(original_ast, original_path, replacements)
102
+ ret = original_ast.to_source(original_path)
103
+ offset = -original_ast.location(original_path).first_index
104
+
105
+ replacements.each do |replacement|
106
+ from = replacement[:from]
107
+ to_code = replacement[:to]
108
+
109
+ ret[(from.first_index + offset)..(from.last_index + offset)] = to_code
110
+ offset += to_code.size - from.size
111
+ end
112
+
113
+ ret
114
+ end
115
+
116
+ private def replace_lvar(ast, path, lvar_suffix: gen_lvar_suffix)
117
+ replacements = []
118
+
119
+ ast.traverse do |node, opt|
120
+ case node.type
121
+ when :LASGN
122
+ next if node.lasgn_opasgn?
123
+
124
+ replacements << {
125
+ from: node.location_variable_name_of_lasgn(path),
126
+ to: "#{node.children[0]}#{lvar_suffix}",
127
+ }
128
+ when :LVAR
129
+ replacements << {
130
+ from: node.location(path),
131
+ to: "#{node.children[0]}#{lvar_suffix}"
132
+ }
133
+ when :OP_ASGN_OR
134
+ opt[:ignore_index] = 0
135
+ when :OP_ASGN_AND
136
+ opt[:ignore_index] = 0
137
+ end
138
+ end
139
+
140
+ replace(ast, path, replacements)
141
+ end
142
+
143
+ private def assign_args(method_node, fcall_node, fcall_path, lvar_suffix)
144
+ params = method_node.children[0]
145
+ args = fcall_node.fcall_args.array_content
146
+ args.map.with_index do |arg, index|
147
+ "#{params[index]}#{lvar_suffix} = #{arg.to_source(fcall_path)}"
148
+ end.join(';') + ';'
149
+ end
150
+
151
+ private def gen_lvar_suffix
152
+ "__#{SecureRandom.hex(5)}"
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,57 @@
1
+ module Rinline
2
+ class Runner
3
+ class << self
4
+ attr_accessor :current
5
+ end
6
+
7
+ attr_accessor :debug, :iseq_threshold
8
+ attr_reader :file_cache
9
+
10
+ def initialize
11
+ @debug = false
12
+ @iseq_threshold = 50
13
+ @file_cache = {}
14
+ end
15
+
16
+ def optimize_instance_method(klass, method_name)
17
+ debug_print "optimizing: #{klass}##{method_name}"
18
+ optimized = Optimizer.optimize(klass, method_name)
19
+ unless optimized
20
+ debug_print "skipped: #{klass}##{method_name}"
21
+ return
22
+ end
23
+
24
+ klass.class_eval "undef :#{method_name}; #{optimized}"
25
+ debug_print "optimized: #{klass}##{method_name}"
26
+ end
27
+
28
+ def optimize_instance_methods(klass)
29
+ klass.instance_methods(false).each do |method|
30
+ optimize_instance_method(klass, method)
31
+ end
32
+ end
33
+
34
+ def optimize_class(klass)
35
+ debug_print "class: #{klass}"
36
+ optimize_instance_methods(klass)
37
+ optimize_instance_methods(klass.singleton_class)
38
+ end
39
+
40
+ alias optimize_module optimize_class
41
+
42
+ def optimize_namespace(mod)
43
+ debug_print "namespace: #{mod}"
44
+ optimize_module(mod)
45
+ constants = mod.constants
46
+ constants -= Struct.constants if mod < Struct
47
+ constants.each do |child|
48
+ child = mod.const_get(child)
49
+ optimize_namespace(child) if child.is_a?(Module)
50
+ end
51
+ end
52
+
53
+ def debug_print(*msg)
54
+ $stderr.puts(*msg) if debug
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Rinline
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,34 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "rinline/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rinline"
7
+ spec.version = Rinline::VERSION
8
+ spec.authors = ["Masataka Pocke Kuwabara"]
9
+ spec.email = ["kuwabara@pocke.me"]
10
+
11
+ spec.summary = %q{Inline expansion for Ruby}
12
+ spec.description = %q{Inline expansion for Ruby}
13
+ spec.homepage = "https://github.com/pocke/rinline"
14
+
15
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", ">= 2"
31
+ spec.add_development_dependency "rake", "~> 12.0"
32
+ spec.add_development_dependency 'minitest', '>= 5'
33
+ spec.add_development_dependency 'rr'
34
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rinline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masataka Pocke Kuwabara
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rr
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Inline expansion for Ruby
70
+ email:
71
+ - kuwabara@pocke.me
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - README.md
79
+ - Rakefile
80
+ - benchmark/simple.rb
81
+ - bin/console
82
+ - bin/setup
83
+ - lib/rinline.rb
84
+ - lib/rinline/ext/ast_ext.rb
85
+ - lib/rinline/ext/iseq_ext.rb
86
+ - lib/rinline/ext/method_ext.rb
87
+ - lib/rinline/location.rb
88
+ - lib/rinline/optimizer.rb
89
+ - lib/rinline/runner.rb
90
+ - lib/rinline/version.rb
91
+ - rinline.gemspec
92
+ homepage: https://github.com/pocke/rinline
93
+ licenses: []
94
+ metadata:
95
+ homepage_uri: https://github.com/pocke/rinline
96
+ source_code_uri: https://github.com/pocke/rinline
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.0.3
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Inline expansion for Ruby
116
+ test_files: []