trailblazer-endpoint 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/Appraisals +5 -0
  4. data/CHANGES.md +26 -0
  5. data/README.md +40 -5
  6. data/Rakefile +7 -1
  7. data/gemfiles/rails_app.gemfile +12 -0
  8. data/lib/trailblazer/endpoint.rb +29 -14
  9. data/lib/trailblazer/endpoint/adapter.rb +30 -121
  10. data/lib/trailblazer/endpoint/builder.rb +1 -1
  11. data/lib/trailblazer/endpoint/controller.rb +223 -0
  12. data/lib/trailblazer/endpoint/dsl.rb +29 -0
  13. data/lib/trailblazer/endpoint/options.rb +92 -0
  14. data/lib/trailblazer/endpoint/protocol.rb +5 -8
  15. data/lib/trailblazer/endpoint/version.rb +1 -1
  16. data/test/adapter/api_test.rb +6 -11
  17. data/test/adapter/web_test.rb +2 -5
  18. data/test/config_test.rb +128 -0
  19. data/test/docs/controller_test.rb +339 -58
  20. data/test/endpoint_test.rb +1 -1
  21. data/test/rails-app/.gitignore +8 -2
  22. data/test/rails-app/.ruby-version +1 -0
  23. data/test/rails-app/Gemfile +15 -9
  24. data/test/rails-app/Gemfile.lock +162 -121
  25. data/test/rails-app/app/concepts/app/api/v1/representer/errors.rb +16 -0
  26. data/test/rails-app/app/concepts/auth/jwt.rb +35 -0
  27. data/test/rails-app/app/concepts/auth/operation/authenticate.rb +32 -0
  28. data/test/rails-app/app/concepts/auth/operation/policy.rb +9 -0
  29. data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
  30. data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
  31. data/test/rails-app/app/concepts/song/operation/create.rb +17 -0
  32. data/test/rails-app/app/concepts/song/operation/show.rb +10 -0
  33. data/test/rails-app/app/concepts/song/representer.rb +5 -0
  34. data/test/rails-app/app/concepts/song/view/create.erb +1 -0
  35. data/test/rails-app/app/concepts/song/view/new.erb +1 -0
  36. data/test/rails-app/app/controllers/api/v1/songs_controller.rb +41 -0
  37. data/test/rails-app/app/controllers/application_controller.rb +8 -1
  38. data/test/rails-app/app/controllers/application_controller/api.rb +107 -0
  39. data/test/rails-app/app/controllers/application_controller/web.rb +44 -0
  40. data/test/rails-app/app/controllers/auth_controller.rb +44 -0
  41. data/test/rails-app/app/controllers/home_controller.rb +5 -0
  42. data/test/rails-app/app/controllers/songs_controller.rb +71 -13
  43. data/test/rails-app/app/models/song.rb +3 -0
  44. data/test/rails-app/app/models/user.rb +7 -0
  45. data/test/rails-app/bin/bundle +114 -0
  46. data/test/rails-app/bin/rails +4 -0
  47. data/test/rails-app/bin/rake +4 -0
  48. data/test/rails-app/bin/setup +33 -0
  49. data/test/rails-app/config/application.rb +26 -3
  50. data/test/rails-app/config/credentials.yml.enc +1 -0
  51. data/test/rails-app/config/database.yml +2 -2
  52. data/test/rails-app/config/environments/development.rb +7 -17
  53. data/test/rails-app/config/environments/production.rb +28 -23
  54. data/test/rails-app/config/environments/test.rb +8 -12
  55. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -4
  56. data/test/rails-app/config/initializers/cors.rb +16 -0
  57. data/test/rails-app/config/initializers/trailblazer.rb +2 -0
  58. data/test/rails-app/config/locales/en.yml +11 -1
  59. data/test/rails-app/config/master.key +1 -0
  60. data/test/rails-app/config/routes.rb +16 -4
  61. data/test/rails-app/db/schema.rb +15 -0
  62. data/test/rails-app/test/controllers/api_songs_controller_test.rb +87 -0
  63. data/test/rails-app/test/controllers/songs_controller_test.rb +80 -147
  64. data/test/rails-app/test/test_helper.rb +7 -1
  65. data/test/test_helper.rb +0 -2
  66. data/trailblazer-endpoint.gemspec +2 -1
  67. metadata +70 -22
  68. data/test/rails-app/config/initializers/cookies_serializer.rb +0 -5
  69. data/test/rails-app/config/initializers/new_framework_defaults.rb +0 -24
  70. data/test/rails-app/config/initializers/session_store.rb +0 -3
  71. data/test/rails-app/config/secrets.yml +0 -22
  72. data/test/rails-app/test/helpers/.keep +0 -0
  73. data/test/rails-app/test/integration/.keep +0 -0
  74. data/test/rails-app/test/mailers/.keep +0 -0
  75. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  76. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class Create < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class New < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module Song::Operation
2
+ class Create < Trailblazer::Operation
3
+ step :contract
4
+ step :model
5
+ # step :validate
6
+ # step :save
7
+
8
+ def model(ctx, params:, **)
9
+ return unless params[:id]
10
+ ctx[:model] = Song.new(params[:id])
11
+ end
12
+
13
+ def contract(ctx, **)
14
+ ctx[:contract] = Struct.new(:errors).new()
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Song::Operation
2
+ class Show < Trailblazer::Operation
3
+ step :model, Output(:failure) => End(:not_found)
4
+
5
+ def model(ctx, params:, **)
6
+ return unless params[:id] == "1"
7
+ ctx[:model] = Song.new(params[:id])
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Song::Representer < Representable::Decorator
2
+ include Representable::JSON
3
+
4
+ property :id
5
+ end
@@ -0,0 +1 @@
1
+ <div><%= model %><%= options[:current_user] %></div>
@@ -0,0 +1 @@
1
+ <div><%= model %></div>
@@ -0,0 +1,41 @@
1
+ #:controller
2
+ module Api
3
+ module V1
4
+ #:endpoint
5
+ class SongsController < ApplicationController::Api
6
+ endpoint Song::Operation::Create
7
+ endpoint Song::Operation::Show do
8
+ {Output(:not_found) => Track(:not_found)} # add additional wiring to {domain_activity}
9
+ end
10
+ #:endpoint end
11
+
12
+ #:create
13
+ def create
14
+ endpoint Song::Operation::Create, representer_class: Song::Representer
15
+ end
16
+ #:create end
17
+
18
+ #~empty
19
+ def show
20
+ endpoint Song::Operation::Show, representer_class: Song::Representer
21
+ end
22
+
23
+ def show_with_options
24
+ endpoint Song::Operation::Show, representer_class: Song::Representer, protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
25
+ end
26
+
27
+ class WithOptionsController < ApplicationController::Api
28
+ endpoint Song::Operation::Show do {Output(:not_found) => Track(:not_found)} end
29
+
30
+ #:show-options
31
+ def show
32
+ endpoint Song::Operation::Show, representer_class: Song::Representer,
33
+ protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
34
+ end
35
+ #:show-options end
36
+ end
37
+ #~empty end
38
+ end
39
+ end
40
+ end
41
+ #:controller end
@@ -1,3 +1,10 @@
1
+ require "trailblazer/endpoint/controller"
2
+
1
3
  class ApplicationController < ActionController::Base
2
- protect_from_forgery with: :exception
4
+ def self.current_user_in_domain_ctx
5
+ ->(_ctx, ((ctx, a), b)) { ctx[:domain_ctx][:current_user] = ctx[:current_user]; [_ctx, [[ctx, a], b]] } # FIXME: extract to lib?
6
+ end
3
7
  end
8
+
9
+
10
+ # directive
@@ -0,0 +1,107 @@
1
+ #:app-controller
2
+ #:app-include
3
+ class ApplicationController::Api < ApplicationController
4
+ include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
5
+ #:app-include end
6
+
7
+ def self.options_for_block_options(ctx, controller:, **)
8
+ response_block = ->(ctx, endpoint_ctx:, **) do
9
+ controller.render json: endpoint_ctx[:representer], status: endpoint_ctx[:status]
10
+ end
11
+
12
+ {
13
+ success_block: response_block,
14
+ failure_block: response_block,
15
+ protocol_failure_block: response_block
16
+ }
17
+ end
18
+
19
+ directive :options_for_block_options, method(:options_for_block_options)
20
+ #:app-controller end
21
+
22
+ #:options_for_endpoint
23
+ def self.options_for_endpoint(ctx, controller:, **)
24
+ {
25
+ request: controller.request,
26
+ errors_representer_class: App::Api::V1::Representer::Errors,
27
+ errors: Trailblazer::Endpoint::Adapter::API::Errors.new,
28
+ }
29
+ end
30
+
31
+ directive :options_for_endpoint, method(:options_for_endpoint)
32
+ #:options_for_endpoint end
33
+
34
+ #:options_for_domain_ctx
35
+ def self.options_for_domain_ctx(ctx, controller:, **) # TODO: move to ApplicationController
36
+ {
37
+ params: controller.params,
38
+ }
39
+ end
40
+
41
+ directive :options_for_domain_ctx, method(:options_for_domain_ctx)
42
+ #:options_for_domain_ctx end
43
+
44
+ #:protocol
45
+ class Protocol < Trailblazer::Endpoint::Protocol
46
+ step Auth::Operation::Policy, inherit: true, id: :policy, replace: :policy
47
+ step Subprocess(Auth::Operation::Authenticate), inherit: true, id: :authenticate, replace: :authenticate
48
+ end
49
+ #:protocol end
50
+
51
+ #:adapter
52
+ module Adapter
53
+ class Representable < Trailblazer::Endpoint::Adapter::API
54
+ step :render # added before End.success
55
+ step :render_errors, after: :_422_status, magnetic_to: :failure, Output(:success) => Track(:failure)
56
+ step :render_errors, after: :protocol_failure, magnetic_to: :fail_fast, Output(:success) => Track(:fail_fast), id: :render_protocol_failure_errors
57
+
58
+ def render(ctx, domain_ctx:, representer_class:, **) # this is what usually happens in your {Responder}.
59
+ ctx[:representer] = representer_class.new(domain_ctx[:model] || raise("no model found!"))
60
+ end
61
+
62
+ def render_errors(ctx, errors:, errors_representer_class:, **) # TODO: extract with {render}
63
+ ctx[:representer] = errors_representer_class.new(errors)
64
+ end
65
+
66
+ Trailblazer::Endpoint::Adapter::API.insert_error_handler_steps!(self)
67
+ include Trailblazer::Endpoint::Adapter::API::Errors::Handlers # handler methods to set an error message.
68
+ end # Representable
69
+ end
70
+ #:adapter end
71
+
72
+ puts Trailblazer::Developer.render(Adapter::Representable)
73
+
74
+ #:endpoint
75
+ # app/controllers/application_controller/api.rb
76
+ endpoint protocol: Protocol, adapter: Adapter::Representable do
77
+ # {Output(:not_found) => Track(:not_found)}
78
+ {}
79
+ end
80
+ #:endpoint end
81
+ end
82
+
83
+
84
+ # header 'Authorization', "Bearer #{result['jwt_token']}" if result['jwt_token']
85
+
86
+
87
+ # ΓΞΞ Protocol ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥
88
+ # | (Start)--->[Authenticate]-->[Policy]-->[Show]----------->((success)) |
89
+ # | | | | L------------->((failure)) |
90
+ # | | | L------------->((not_found)) |
91
+ # | | L------------------->((not_authorized)) |
92
+ # | L----------------------------->((not_authenticated)) |
93
+ # | ((invalid_data)) |
94
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩
95
+ #--˥˩ ˪
96
+
97
+ # ΓΞΞ Adapter::Representable ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥
98
+ # | |
99
+ # | (Start)---> ΓΞΞ Protocol ΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˥ |
100
+ # | (Start)--->[Authenticate]-->[Policy]-->[Show]----------->((success)) |--->[_200_status]--->[render]---------------------------------------------------------------->((success)) |
101
+ # | | | | L------------->((failure)) |-˥ |
102
+ # | | | L------------->((not_found)) |-|------------------------------->[_404_status]-----------v |
103
+ # | | L------------------->((not_authorized)) |-|->[handle_not_authorized]------>[_403_status]--->[protocol_failure]--->[render_errors]--->((fail_fast)) |
104
+ # | L----------------------------->((not_authenticated)) |-|->[handle_not_authenticated]--->[_401_status]-----------^ |
105
+ # | ((invalid_data)) |-┴->[handle_invalid_data]-------->[_422_status]--->[render_errors]--------------------------->((failure)) |
106
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩ |
107
+ # LΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞ˩
@@ -0,0 +1,44 @@
1
+ #:app-include
2
+ #:options
3
+ #:protocol
4
+ #:generic
5
+ class ApplicationController::Web < ApplicationController
6
+ #~pskip
7
+ #~gskip
8
+ include Trailblazer::Endpoint::Controller.module(dsl: true, application_controller: true)
9
+ #:app-include end
10
+
11
+ def self.options_for_endpoint(ctx, controller:, **)
12
+ {
13
+ session: controller.session,
14
+ }
15
+ end
16
+
17
+ directive :options_for_endpoint, method(:options_for_endpoint)
18
+ #:options end
19
+
20
+ # directive :options_for_flow_options, method(:options_for_flow_options)
21
+ # directive :options_for_block_options, method(:options_for_block_options)
22
+ #~pskip end
23
+ class Protocol < Trailblazer::Endpoint::Protocol
24
+ # provide method for {step :authenticate}
25
+ def authenticate(ctx, session:, **)
26
+ ctx[:current_user] = User.find_by(id: session[:user_id])
27
+ end
28
+
29
+ # provide method for {step :policy}
30
+ def policy(ctx, domain_ctx:, **)
31
+ Policy.(domain_ctx)
32
+ end
33
+ end
34
+ #:protocol end
35
+ Policy = ->(domain_ctx) { domain_ctx[:params][:policy] == "false" ? false : true }
36
+ #~gskip end
37
+ endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
38
+ domain_ctx_filter: ApplicationController.current_user_in_domain_ctx
39
+ end
40
+ #:generic end
41
+
42
+ # do
43
+ # {Output(:not_found) => Track(:not_found)}
44
+ # end
@@ -0,0 +1,44 @@
1
+ class AuthController < ApplicationController::Web
2
+ # We could use a fully-fledged operation here, with a contract and whatnot.
3
+ def self.authenticate(ctx, request:, params:, **)
4
+ if params[:username] == "yogi@trb.to" && params[:password] == "secret"
5
+ ctx[:current_user] = User.find_by(email: params[:username])
6
+
7
+ return true
8
+ else
9
+ return false # let's be extra explicit!
10
+ end
11
+ end
12
+
13
+ endpoint("sign_in", domain_activity: Class.new(Trailblazer::Activity::Railway)) do
14
+ # step nil, delete: :domain_activity
15
+ step nil, delete: :policy
16
+ step AuthController.method(:authenticate), replace: :authenticate, inherit: true, id: :authenticate
17
+
18
+ {}
19
+ end
20
+
21
+ def self.options_for_endpoint(ctx, controller:, **)
22
+ {
23
+ params: controller.params,
24
+ request: controller.request,
25
+ }
26
+ end
27
+
28
+ directive :options_for_endpoint, method(:options_for_endpoint)
29
+
30
+ def sign_in
31
+ endpoint "sign_in" do |ctx, current_user:, **|
32
+ session[:user_id] = current_user.id # Working on {session} is HTTP-specific and done in the controller.
33
+
34
+ redirect_to dashboard_path
35
+ # render html: "Yes!"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def dashboard_path
42
+ "/"
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ class HomeController < ApplicationController
2
+ def dashboard
3
+ render html: "Yo!"
4
+ end
5
+ end
@@ -1,26 +1,84 @@
1
- class SongsController < ApplicationController
2
- require "trailblazer/endpoint/rails"
3
- include Trailblazer::Endpoint::Controller
1
+ #:endpoint
2
+ #:or
3
+ #:create
4
+ class SongsController < ApplicationController::Web
5
+ endpoint Song::Operation::Create
4
6
 
7
+ #:endpoint end
5
8
  def create
6
- endpoint Create, path: songs_path, args: [ params, { "user.current" => ::Module } ]
9
+ endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
10
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
11
+ end.Or do |ctx, contract:, **| # validation failure
12
+ render html: cell(Song::Cell::New, contract)
13
+ end
7
14
  end
15
+ #:create end
8
16
 
9
- def update
10
- endpoint Update, path: songs_path, args: [params]
17
+ #~oskip
18
+ class CreateOrController < SongsController
19
+ #~oskip end
20
+ def create
21
+ endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
22
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
23
+ end.Or do |ctx, contract:, **| # validation failure
24
+ render json: contract.errors, status: 422
25
+ end
11
26
  end
27
+ end
28
+ #:or end
12
29
 
13
- def update_with_user
14
- endpoint Update, path: songs_path, args: [ params, { "user.current" => ::Module } ]
30
+ def create_without_block
31
+ endpoint Song::Operation::Create
15
32
  end
16
33
 
17
- def show
18
- endpoint Show, path: songs_path, args: [ params, { "user.current" => ::Module } ]
34
+ class CreateWithOptionsController < SongsController
35
+ #:create-options
36
+ def create
37
+ endpoint Song::Operation::Create, session: {user_id: 2} do |ctx, current_user:, model:, **|
38
+ render html: cell(Song::Cell::Create, model, current_user: current_user)
39
+ end
40
+ end
41
+ #:create-options end
19
42
  end
20
43
 
21
- def create_with_custom_handlers
22
- endpoint Create, path: songs_path, args: [ params, { "user.current" => ::Module } ] do |m|
23
- m.created { |result| render json: result["representer.serializer.class"].new(result["model"]), status: 999 }
44
+
45
+ class CreateWithOptionsForDomainCtxController < SongsController
46
+ #:domain_ctx
47
+ def create
48
+ endpoint Song::Operation::Create, options_for_domain_ctx: {params: {id: 999}} do |ctx, model:, **|
49
+ render html: cell(Song::Cell::Create, model)
50
+ end
24
51
  end
52
+ #:domain_ctx end
53
+ end
54
+
55
+ class CreateEndpointCtxController < SongsController
56
+ #:endpoint_ctx
57
+ def create
58
+ endpoint Song::Operation::Create do |ctx, endpoint_ctx:, **|
59
+ render html: "Created", status: endpoint_ctx[:status]
60
+ end.Or do |ctx, **| # validation failure
61
+ #~empty
62
+ #~empty end
63
+ end
64
+ end
65
+ #:endpoint_ctx end
66
+ end
67
+
68
+ # end.Or do |ctx, endpoint_ctx:, **| # validation failure
69
+ # render json: endpoint_ctx.keys, status: 422
70
+ # end
71
+
72
+
73
+ class CreateWithProtocolFailureController < SongsController
74
+ #:protocol_failure
75
+ def create_with_protocol_failure
76
+ endpoint Song::Operation::Create do |ctx, **|
77
+ redirect_to dashboard_path
78
+ end.protocol_failure do |ctx, **|
79
+ render html: "wrong login, app crashed", status: 500
80
+ end
81
+ end
82
+ #:protocol_failure end
25
83
  end
26
84
  end
@@ -0,0 +1,3 @@
1
+ class Song < Struct.new(:id)
2
+
3
+ end
@@ -0,0 +1,7 @@
1
+ class User < Struct.new(:id, :email)
2
+ def self.find_by(id: false, email: false)
3
+ return User.new(1, "yogi@trb.to") if email == "yogi@trb.to"
4
+ return User.new(id, "yogi@trb.to") if id.to_s == "1"
5
+ return User.new(id, "seuros@trb.to") if id.to_s == "2"
6
+ end
7
+ end
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+ bundler_version = nil
28
+ update_index = nil
29
+ ARGV.each_with_index do |a, i|
30
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
+ bundler_version = a
32
+ end
33
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
+ bundler_version = $1
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../../Gemfile", __FILE__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+ lockfile_contents = File.read(lockfile)
59
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
+ Regexp.last_match(1)
61
+ end
62
+
63
+ def bundler_version
64
+ @bundler_version ||=
65
+ env_var_version || cli_arg_version ||
66
+ lockfile_version
67
+ end
68
+
69
+ def bundler_requirement
70
+ return "#{Gem::Requirement.default}.a" unless bundler_version
71
+
72
+ bundler_gem_version = Gem::Version.new(bundler_version)
73
+
74
+ requirement = bundler_gem_version.approximate_recommendation
75
+
76
+ return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77
+
78
+ requirement += ".a" if bundler_gem_version.prerelease?
79
+
80
+ requirement
81
+ end
82
+
83
+ def load_bundler!
84
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
85
+
86
+ activate_bundler
87
+ end
88
+
89
+ def activate_bundler
90
+ gem_error = activation_error_handling do
91
+ gem "bundler", bundler_requirement
92
+ end
93
+ return if gem_error.nil?
94
+ require_error = activation_error_handling do
95
+ require "bundler/version"
96
+ end
97
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99
+ exit 42
100
+ end
101
+
102
+ def activation_error_handling
103
+ yield
104
+ nil
105
+ rescue StandardError, LoadError => e
106
+ e
107
+ end
108
+ end
109
+
110
+ m.load_bundler!
111
+
112
+ if m.invoked_as_script?
113
+ load Gem.bin_path("bundler", "bundle")
114
+ end