type_fusion 0.0.1 → 0.0.3

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
  SHA256:
3
- metadata.gz: ab5cbd551a4587ba748b987afef919d5f6c76e78ebf2ddeebdb42a37f57da3d8
4
- data.tar.gz: 637f311b870a08bf9089fa4d254e79a1e72c5a2a0fa8946e7d7e962cf6bb54d3
3
+ metadata.gz: 7fd6c97b4b4bb6eeecea4ff23e108c24a7564ac3d85e2231352824cd27bf160e
4
+ data.tar.gz: a438ca7330e335d8d1922c967a24ef46a23403d6825e0e7a4740aafa6e93dcf6
5
5
  SHA512:
6
- metadata.gz: d0404f787af472e5667ff5b2df86ba6b5bf4dd788f4d7cc92c611fe1042e2ac73a83c6ebb4d2630743cea6d141bb796e4b25979deb988ab806174d2f4073f7c7
7
- data.tar.gz: ccbbd9629bc53af4f6a70b5bb2dff059b5b11b7b0732a628a93ea02ff5d3e8eb89e9aa09ce80b9bdc80284730045838f8f8deb29fffb592e63a6af56c4c2a992
6
+ metadata.gz: 48d5ea9c6b6c52e8fe44bd8b1ea295f96ff5e48e737985b7c75d21b5c8379d2934b8dfe6e0fa41acfc85aa4104427a7e86354ebed07823d81e37c85a5060d7e3
7
+ data.tar.gz: 54d0eae75e1b154a249ddf5eecf6facf7694d3fc43d0ea3e3c022e7225a0510f7e2d58758fbbba885f6b45c16f35236653054e3e1d9ea199b6dfb16696b88fa3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.3] - 2023-08-13
4
+
5
+ - Introduce `TypeFusion::Middleware` to allow type-sampling in Rack-powered apps
6
+ - Introduce `TypeFusion::Railtie` to automatically setup type-sampling in Rails apps
7
+ - Introduce `TypeFusion::Config` to control sampling
8
+ - Add `config.type_sample_call_rate` config
9
+ - Add `config.type_sample_request` config
10
+ - Add `config.type_sample_tracepoint_path` config
11
+
12
+ ## [0.0.2] - 2023-08-12
13
+
14
+ - Initial Proof-of-Concept release
15
+ - Implementation of the `TypeFusion::Sampler` class
16
+
3
17
  ## [0.0.1] - 2023-08-11
4
18
 
5
19
  - Initial release
data/Gemfile CHANGED
@@ -7,3 +7,5 @@ gemspec
7
7
  gem "minitest", "~> 5.0"
8
8
  gem "rake", "~> 13.0"
9
9
  gem "rubocop", "~> 1.21"
10
+
11
+ gem "litestack", github: "marcoroth/litestack"
data/Gemfile.lock CHANGED
@@ -1,19 +1,42 @@
1
+ GIT
2
+ remote: https://github.com/marcoroth/litestack.git
3
+ revision: 1911d5ef35f474425fb4e17a11a7d40deb4008a8
4
+ specs:
5
+ litestack (0.2.6)
6
+ erubi
7
+ hanami-router
8
+ oj
9
+ rack
10
+ sqlite3
11
+ tilt
12
+
1
13
  PATH
2
14
  remote: .
3
15
  specs:
4
- type_fusion (0.0.1)
16
+ type_fusion (0.0.3)
17
+ litestack
5
18
 
6
19
  GEM
7
20
  remote: https://rubygems.org/
8
21
  specs:
9
22
  ast (2.4.2)
23
+ erubi (1.12.0)
24
+ hanami-router (0.6.2)
25
+ hanami-utils (~> 0.7)
26
+ http_router (~> 0.11)
27
+ hanami-utils (0.9.2)
28
+ http_router (0.11.2)
29
+ rack (>= 1.0.0)
30
+ url_mount (~> 0.2.1)
10
31
  json (2.6.3)
11
32
  minitest (5.19.0)
33
+ oj (3.15.1)
12
34
  parallel (1.23.0)
13
35
  parser (3.2.2.3)
14
36
  ast (~> 2.4.1)
15
37
  racc
16
38
  racc (1.7.1)
39
+ rack (3.0.8)
17
40
  rainbow (3.1.1)
18
41
  rake (13.0.6)
19
42
  regexp_parser (2.8.1)
@@ -31,13 +54,19 @@ GEM
31
54
  rubocop-ast (1.29.0)
32
55
  parser (>= 3.2.1.0)
33
56
  ruby-progressbar (1.13.0)
57
+ sqlite3 (1.6.3-x86_64-darwin)
58
+ sqlite3 (1.6.3-x86_64-linux)
59
+ tilt (2.2.0)
34
60
  unicode-display_width (2.4.2)
61
+ url_mount (0.2.1)
62
+ rack
35
63
 
36
64
  PLATFORMS
37
65
  x86_64-darwin-22
38
66
  x86_64-linux
39
67
 
40
68
  DEPENDENCIES
69
+ litestack!
41
70
  minitest (~> 5.0)
42
71
  rake (~> 13.0)
43
72
  rubocop (~> 1.21)
data/README.md CHANGED
@@ -14,9 +14,117 @@ If bundler is not being used to manage dependencies, install the gem by executin
14
14
  gem install type_fusion
15
15
  ```
16
16
 
17
+ #### Rack
18
+
19
+ ```ruby
20
+ require "type_fusion/rack/middleware"
21
+
22
+ use TypeFusion::Middleware
23
+ ```
24
+
25
+ #### Rails
26
+
27
+ Adding the gem to your applications Gemfile will automatically setup `type_fusion`.
28
+
29
+
30
+ ## Configuration
31
+
32
+ Setup `TypeFusion` in an initializer
33
+
34
+ ```ruby
35
+ # config/initializers/type_fusion.rb
36
+
37
+ require "type_fusion"
38
+
39
+ TypeFusion.config do |config|
40
+
41
+ # === type_sample_request
42
+ #
43
+ # Set type_sample_request to a lambda which resolves to true/false
44
+ # to set if type sampling should be enabled for the whole rack request.
45
+ #
46
+ # Default: ->(rack_env) { [true, false, false, false].sample }
47
+ #
48
+ # config.type_sample_request = ->(rack_env) { [true, false, false, false].sample }
49
+
50
+
51
+ # === type_sample_tracepoint_path
52
+ #
53
+ # Set type_sample_tracepoint_path to a lambda which resolves
54
+ # to true/false to check if a tracepoint_path should be sampled
55
+ # or not.
56
+ #
57
+ # This can be useful when you only want to sample method calls for
58
+ # certain gems or want to exclude a gem from being sampled.
59
+ #
60
+ # Example:
61
+ # config.type_sample_tracepoint_path = ->(tracepoint_path) {
62
+ # # only sample calls for the Nokogiri gem
63
+ # tracepoint_path.include?("nokogiri")
64
+ # }
65
+ #
66
+ # Default: ->(tracepoint_path) { true }
67
+ #
68
+ # config.type_sample_tracepoint_path = ->(tracepoint_path) { true }
69
+
70
+
71
+ # === type_sample_call_rate
72
+ #
73
+ # Set type_sample_call_rate to 1.0 to capture 100% of method calls
74
+ # within a rack request.
75
+ #
76
+ # Default: 0.001
77
+ #
78
+ # config.type_sample_call_rate = 0.001
79
+ end
80
+ ```
81
+
17
82
  ## Usage
18
83
 
19
- TODO: Write usage instructions here
84
+ #### Type sample inside a block
85
+
86
+ ```ruby
87
+ TypeFusion.with_sampling do
88
+ # run code you want to type sample here
89
+ end
90
+ ```
91
+
92
+ #### Type sample globally
93
+
94
+ ```ruby
95
+ TypeFusion.start
96
+
97
+ # run code you want to type sample here
98
+
99
+ TypeFusion.stop
100
+ ```
101
+
102
+ #### Retrieve the samples
103
+
104
+ ```ruby
105
+ TypeFusion::Sampler.instance.samples
106
+ # => [...]
107
+ ```
108
+
109
+ ```ruby
110
+ TypeFusion::Sampler.instance.samples.first
111
+
112
+ # => #<struct TypeFusion::SampleCall
113
+ # gem_and_version="nokogiri-1.15.4-x86_64-darwin",
114
+ # receiver="Nokogiri",
115
+ # method_name=:parse,
116
+ # location=[
117
+ # "/Users/marcoroth/.anyenv/envs/rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/nokogiri-1.15.4-x86_64-darwin/lib/nokogiri.rb",
118
+ # 43
119
+ # ],
120
+ # parameters=[
121
+ # [:string, :req, String],
122
+ # [:url, :opt, NilClass],
123
+ # [:encoding, :opt, NilClass],
124
+ # [:options, :opt, NilClass]
125
+ # ]
126
+ # >
127
+ ```
20
128
 
21
129
  ## Development
22
130
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module TypeFusion
6
+ class << self
7
+ def config
8
+ @config ||= Config.instance
9
+
10
+ yield @config if block_given?
11
+
12
+ @config
13
+ end
14
+ end
15
+
16
+ class Config
17
+ include Singleton
18
+
19
+ attr_accessor :type_sample_call_rate, :type_sample_request, :type_sample_tracepoint_path
20
+
21
+ def initialize
22
+ @type_sample_call_rate = 0.001
23
+ @type_sample_request = ->(_env) { [true, false, false, false].sample }
24
+ @type_sample_tracepoint_path = ->(_tracepoint_path) { true }
25
+ end
26
+
27
+ def type_sample_request?(env)
28
+ type_sample_request&.call(env)
29
+ end
30
+
31
+ def type_sample_tracepoint_path?(env)
32
+ type_sample_tracepoint_path&.call(env)
33
+ end
34
+
35
+ def type_sample_call?
36
+ type_sample_call_rate > rand
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "type_fusion/sampler"
4
+
5
+ module TypeFusion
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ if TypeFusion.config.type_sample_request?(env)
13
+ puts "[TypeFusion] Type-sampling this request"
14
+ TypeFusion.start
15
+ end
16
+
17
+ @app.call(env)
18
+ ensure
19
+ TypeFusion.stop
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeFusion
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "type_fusion.middleware" do |app|
6
+ require "type_fusion/rack/middleware"
7
+
8
+ app.config.middleware.use TypeFusion::Middleware
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TypeFusion
6
+ SampleCall = Struct.new(:gem_and_version, :receiver, :method_name, :location, :parameters) do
7
+ def to_s
8
+ JSON.pretty_generate(to_h)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "litestack"
4
+
5
+ module TypeFusion
6
+ class SampleJob
7
+ include Litejob
8
+
9
+ self.queue = :default
10
+
11
+ def perform(sample)
12
+ puts sample.inspect
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module TypeFusion
6
+ class Sampler
7
+ include Singleton
8
+
9
+ attr_accessor :samples
10
+
11
+ def initialize
12
+ @samples = []
13
+ end
14
+
15
+ def with_sampling
16
+ trace.enable
17
+
18
+ yield if block_given?
19
+
20
+ trace.disable
21
+ end
22
+
23
+ def trace
24
+ @trace ||= TracePoint.trace(:call) do |tracepoint|
25
+ if sample?(tracepoint.path)
26
+ receiver = begin
27
+ tracepoint.binding.receiver.name
28
+ rescue StandardError
29
+ tracepoint.binding.receiver.class.name
30
+ end
31
+
32
+ method_name = tracepoint.method_id
33
+ location = tracepoint.binding.source_location
34
+ gem_and_version = location.first.gsub(gem_path, "").split("/").first
35
+ args = tracepoint.parameters.map(&:reverse).to_h
36
+ parameters = extract_parameters(args, tracepoint.binding)
37
+
38
+ sample = SampleCall.new(
39
+ gem_and_version: gem_and_version,
40
+ receiver: receiver,
41
+ method_name: method_name,
42
+ location: location,
43
+ parameters: parameters,
44
+ )
45
+
46
+ samples << sample
47
+ SampleJob.perform_async(sample)
48
+ end
49
+ end.tap(&:disable)
50
+ end
51
+
52
+ def to_s
53
+ inspect
54
+ end
55
+
56
+ def inspect
57
+ "#<TypeFusion::Sampler sample_count=#{@samples.count}>"
58
+ end
59
+
60
+ def reset!
61
+ @samples = []
62
+ @trace = nil
63
+ end
64
+
65
+ private
66
+
67
+ def sample?(tracepoint_path)
68
+ TypeFusion.config.type_sample_call? &&
69
+ TypeFusion.config.type_sample_tracepoint_path?(tracepoint_path) &&
70
+ tracepoint_path.start_with?(gem_path)
71
+ end
72
+
73
+ def type_for_object(object)
74
+ case object
75
+ when Hash
76
+ ["Hash", object.map { |key, value| [key, type_for_object(value)] }]
77
+ when Array
78
+ ["Array", object.map { |value| type_for_object(value) }]
79
+ else
80
+ object.class
81
+ end
82
+ end
83
+
84
+ def extract_parameters(args, binding)
85
+ args.map do |name, kind|
86
+ variable = name.to_s.gsub("*", "").gsub("&", "").to_sym
87
+
88
+ type = if binding.local_variables.include?(variable)
89
+ type_for_object(binding.local_variable_get(variable))
90
+ else
91
+ # *, ** or &
92
+ "unused"
93
+ end
94
+
95
+ [name, kind, type]
96
+ end
97
+ end
98
+
99
+ def gem_path
100
+ "#{Gem.default_path.last}/gems/"
101
+ end
102
+ end
103
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypeFusion
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.3"
5
5
  end
data/lib/type_fusion.rb CHANGED
@@ -1,6 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "type_fusion/version"
4
+ require_relative "type_fusion/config"
5
+ require_relative "type_fusion/sample_call"
6
+ require_relative "type_fusion/sample_job"
7
+ require_relative "type_fusion/sampler"
4
8
 
5
9
  module TypeFusion
10
+ class << self
11
+ def start
12
+ return if Sampler.instance.trace.enabled?
13
+
14
+ puts "[TypeFusion] Starting Sampler..."
15
+ Sampler.instance.trace.enable
16
+ end
17
+
18
+ def stop
19
+ return unless Sampler.instance.trace.enabled?
20
+
21
+ puts "[TypeFusion] Stopping Sampler..."
22
+ Sampler.instance.trace.disable
23
+ end
24
+
25
+ def with_sampling
26
+ start
27
+
28
+ yield if block_given?
29
+
30
+ stop
31
+ end
32
+ end
6
33
  end
34
+
35
+ require_relative "type_fusion/rails/railtie" if defined?(Rails::Railtie)
data/test.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "lib/type_fusion"
5
+
6
+ TypeFusion.config do |config|
7
+ config.type_sample_call_rate = 1.0
8
+
9
+ config.type_sample_tracepoint_path = lambda do |tracepoint_path|
10
+ tracepoint_path.include?("nokogiri")
11
+ end
12
+ end
13
+
14
+ TypeFusion.with_sampling do
15
+ Nokogiri.parse("<h1></h1>")
16
+ end
17
+
18
+ puts TypeFusion::Sampler.instance.samples
19
+ puts TypeFusion::Sampler.instance.to_s
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: type_fusion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-11 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: litestack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Community-contributed sample data for Ruby types
14
28
  email:
15
29
  - marco.roth@intergga.ch
@@ -26,8 +40,15 @@ files:
26
40
  - README.md
27
41
  - Rakefile
28
42
  - lib/type_fusion.rb
43
+ - lib/type_fusion/config.rb
44
+ - lib/type_fusion/rack/middleware.rb
45
+ - lib/type_fusion/rails/railtie.rb
46
+ - lib/type_fusion/sample_call.rb
47
+ - lib/type_fusion/sample_job.rb
48
+ - lib/type_fusion/sampler.rb
29
49
  - lib/type_fusion/version.rb
30
50
  - sig/type_fusion.rbs
51
+ - test.rb
31
52
  homepage: https://github.com/marcoroth/type_fusion
32
53
  licenses: []
33
54
  metadata: