wayfarer 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +1 -1
- data/Gemfile.lock +20 -15
- data/docs/cookbook/user_agent.md +1 -1
- data/docs/guides/browser_automation/capybara.md +64 -1
- data/docs/guides/browser_automation/custom_adapters.md +100 -0
- data/docs/guides/browser_automation/ferrum.md +3 -3
- data/docs/guides/browser_automation/selenium.md +7 -5
- data/docs/guides/callbacks.md +117 -10
- data/docs/guides/configuration.md +16 -10
- data/docs/guides/error_handling.md +9 -5
- data/docs/guides/networking.md +77 -3
- data/docs/index.md +9 -1
- data/docs/reference/api/base.md +4 -4
- data/docs/reference/configuration_keys.md +42 -0
- data/docs/reference/environment_variables.md +25 -27
- data/lib/wayfarer/base.rb +7 -17
- data/lib/wayfarer/callbacks.rb +71 -0
- data/lib/wayfarer/cli/base.rb +5 -1
- data/lib/wayfarer/cli/job.rb +7 -3
- data/lib/wayfarer/cli/route.rb +2 -2
- data/lib/wayfarer/cli/route_printer.rb +7 -7
- data/lib/wayfarer/config/capybara.rb +10 -0
- data/lib/wayfarer/config/ferrum.rb +11 -0
- data/lib/wayfarer/config/networking.rb +26 -0
- data/lib/wayfarer/config/redis.rb +14 -0
- data/lib/wayfarer/config/root.rb +11 -0
- data/lib/wayfarer/config/selenium.rb +21 -0
- data/lib/wayfarer/config/strconv.rb +45 -0
- data/lib/wayfarer/config/struct.rb +72 -0
- data/lib/wayfarer/gc.rb +3 -7
- data/lib/wayfarer/middleware/fetch.rb +7 -3
- data/lib/wayfarer/middleware/router.rb +2 -2
- data/lib/wayfarer/middleware/worker.rb +12 -9
- data/lib/wayfarer/networking/capybara.rb +28 -0
- data/lib/wayfarer/networking/context.rb +36 -0
- data/lib/wayfarer/networking/ferrum.rb +17 -52
- data/lib/wayfarer/networking/http.rb +34 -0
- data/lib/wayfarer/networking/pool.rb +15 -10
- data/lib/wayfarer/networking/result.rb +1 -1
- data/lib/wayfarer/networking/selenium.rb +20 -47
- data/lib/wayfarer/networking/strategy.rb +38 -0
- data/lib/wayfarer/page.rb +2 -3
- data/lib/wayfarer/redis/pool.rb +3 -1
- data/lib/wayfarer/routing/dsl.rb +8 -8
- data/lib/wayfarer/routing/matchers/custom.rb +23 -0
- data/lib/wayfarer/routing/matchers/host.rb +19 -0
- data/lib/wayfarer/routing/matchers/path.rb +48 -0
- data/lib/wayfarer/routing/matchers/query.rb +63 -0
- data/lib/wayfarer/routing/matchers/scheme.rb +17 -0
- data/lib/wayfarer/routing/matchers/suffix.rb +17 -0
- data/lib/wayfarer/routing/matchers/url.rb +17 -0
- data/lib/wayfarer/routing/route.rb +1 -1
- data/lib/wayfarer.rb +9 -9
- data/spec/base_spec.rb +14 -0
- data/spec/callbacks_spec.rb +102 -0
- data/spec/cli/job_spec.rb +6 -6
- data/spec/config/capybara_spec.rb +18 -0
- data/spec/config/ferrum_spec.rb +24 -0
- data/spec/config/networking_spec.rb +73 -0
- data/spec/config/redis_spec.rb +32 -0
- data/spec/config/root_spec.rb +31 -0
- data/spec/config/selenium_spec.rb +56 -0
- data/spec/config/strconv_spec.rb +58 -0
- data/spec/config/struct_spec.rb +66 -0
- data/spec/gc_spec.rb +8 -6
- data/spec/middleware/fetch_spec.rb +20 -8
- data/spec/middleware/router_spec.rb +7 -0
- data/spec/middleware/worker_spec.rb +64 -27
- data/spec/networking/capybara_spec.rb +12 -0
- data/spec/networking/context_spec.rb +127 -0
- data/spec/networking/ferrum_spec.rb +6 -22
- data/spec/networking/http_spec.rb +12 -0
- data/spec/networking/pool_spec.rb +37 -12
- data/spec/networking/selenium_spec.rb +6 -22
- data/spec/networking/strategy.rb +170 -0
- data/spec/redis/pool_spec.rb +1 -1
- data/spec/routing/dsl_spec.rb +10 -10
- data/spec/routing/integration_spec.rb +22 -22
- data/spec/routing/{custom_matcher_spec.rb → matchers/custom_spec.rb} +4 -4
- data/spec/routing/{host_matcher_spec.rb → matchers/host_spec.rb} +6 -6
- data/spec/routing/{path_matcher_spec.rb → matchers/path_spec.rb} +6 -6
- data/spec/routing/{query_matcher_spec.rb → matchers/query_spec.rb} +15 -15
- data/spec/routing/{scheme_matcher_spec.rb → matchers/scheme_spec.rb} +4 -4
- data/spec/routing/{suffix_matcher_spec.rb → matchers/suffix_spec.rb} +4 -4
- data/spec/routing/{uri_matcher_spec.rb → matchers/uri_spec.rb} +4 -4
- data/spec/routing/path_finder_spec.rb +1 -1
- data/spec/routing/root_route_spec.rb +2 -2
- data/spec/routing/route_spec.rb +2 -2
- data/spec/spec_helpers.rb +13 -5
- data/spec/wayfarer_spec.rb +1 -1
- data/wayfarer.gemspec +8 -7
- metadata +74 -33
- data/lib/wayfarer/config.rb +0 -67
- data/lib/wayfarer/networking/healer.rb +0 -21
- data/lib/wayfarer/networking/net_http.rb +0 -52
- data/lib/wayfarer/routing/custom_matcher.rb +0 -21
- data/lib/wayfarer/routing/host_matcher.rb +0 -23
- data/lib/wayfarer/routing/path_matcher.rb +0 -46
- data/lib/wayfarer/routing/query_matcher.rb +0 -67
- data/lib/wayfarer/routing/scheme_matcher.rb +0 -21
- data/lib/wayfarer/routing/suffix_matcher.rb +0 -21
- data/lib/wayfarer/routing/url_matcher.rb +0 -21
- data/spec/config_spec.rb +0 -144
- data/spec/networking/adapter.rb +0 -135
- data/spec/networking/healer_spec.rb +0 -46
- data/spec/networking/net_http_spec.rb +0 -37
@@ -6,6 +6,7 @@ module Wayfarer
|
|
6
6
|
def self.included(base)
|
7
7
|
base.include(Wayfarer::Redis::Connection)
|
8
8
|
base.include(Wayfarer::Middleware::Stage::API)
|
9
|
+
base.include(Wayfarer::Callbacks)
|
9
10
|
base.include(InstanceMethods)
|
10
11
|
base.extend(ClassMethods)
|
11
12
|
end
|
@@ -21,25 +22,27 @@ module Wayfarer
|
|
21
22
|
module InstanceMethods
|
22
23
|
extend Forwardable
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
delegate %i[params adapter] => "task.metadata"
|
27
|
-
delegate %i[browser capybara] => :adapter
|
25
|
+
delegate %i[params agent] => "task.metadata"
|
28
26
|
|
29
27
|
def call(task)
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
run_callbacks :action do
|
29
|
+
public_send(task.metadata.action)
|
30
|
+
yield if block_given? # TODO: Should be excluded from callback block
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
34
|
def chain
|
36
|
-
Wayfarer::Middleware::Chain.new([*Wayfarer.
|
35
|
+
Wayfarer::Middleware::Chain.new([*Wayfarer.middleware, self])
|
36
|
+
end
|
37
|
+
|
38
|
+
def browser
|
39
|
+
agent.instance
|
37
40
|
end
|
38
41
|
|
39
42
|
def page(live: false)
|
40
43
|
return task.metadata.page unless live
|
41
44
|
|
42
|
-
task.metadata.page =
|
45
|
+
task.metadata.page = agent.live&.page || task.metadata.page
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Networking
|
5
|
+
class Capybara
|
6
|
+
include Strategy
|
7
|
+
|
8
|
+
def create
|
9
|
+
::Capybara::Session.new(Wayfarer.config.capybara.driver, nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy(instance)
|
13
|
+
instance.quit
|
14
|
+
end
|
15
|
+
|
16
|
+
def navigate(instance, url)
|
17
|
+
instance.visit(url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def live(instance)
|
21
|
+
success(url: instance.current_url,
|
22
|
+
body: instance.html,
|
23
|
+
status_code: instance.status_code,
|
24
|
+
headers: instance.response_headers)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Networking
|
5
|
+
Context = Struct.new(:strategy) do
|
6
|
+
def fetch(url)
|
7
|
+
supervise { strategy.fetch(instance, url) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def live
|
11
|
+
supervise { strategy.live(instance) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def renew
|
15
|
+
strategy.destroy(instance)
|
16
|
+
ensure
|
17
|
+
@instance = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def instance
|
21
|
+
@instance ||= strategy.create
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def supervise
|
27
|
+
yield
|
28
|
+
rescue *strategy.renew_on => e
|
29
|
+
renew
|
30
|
+
ensure
|
31
|
+
# If renewing raises, re-raise the originally caught exception
|
32
|
+
raise e if e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -3,67 +3,32 @@
|
|
3
3
|
module Wayfarer
|
4
4
|
module Networking
|
5
5
|
class Ferrum
|
6
|
-
|
7
|
-
[::Ferrum::DeadBrowserError]
|
8
|
-
end
|
9
|
-
|
10
|
-
attr_reader :browser
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@browser = instantiate_browser
|
14
|
-
end
|
15
|
-
|
16
|
-
def fetch(url)
|
17
|
-
browser.goto(url)
|
18
|
-
Result::Success.new(live(nil))
|
19
|
-
end
|
20
|
-
|
21
|
-
def live(_)
|
22
|
-
Wayfarer::Page.new(url: browser.current_url,
|
23
|
-
body: body,
|
24
|
-
status_code: browser.network.response.status,
|
25
|
-
headers: browser.network.response.headers)
|
26
|
-
end
|
6
|
+
include Strategy
|
27
7
|
|
28
|
-
def
|
29
|
-
|
8
|
+
def renew_on
|
9
|
+
[::Ferrum::DeadBrowserError]
|
30
10
|
end
|
31
11
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
12
|
+
def create
|
13
|
+
::Ferrum::Browser.new(Wayfarer.config.ferrum.options).tap do |browser|
|
14
|
+
browser.headers.set(Wayfarer.config.network.http_headers)
|
15
|
+
end
|
35
16
|
end
|
36
17
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
@browser = nil
|
41
|
-
@capybara = nil
|
18
|
+
def destroy(instance)
|
19
|
+
instance.reset
|
20
|
+
instance.quit
|
42
21
|
end
|
43
22
|
|
44
|
-
def
|
45
|
-
|
23
|
+
def navigate(instance, url)
|
24
|
+
instance.goto(url)
|
46
25
|
end
|
47
26
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def instantiate_capybara_driver
|
57
|
-
Capybara.run_server = false
|
58
|
-
Capybara.current_driver = :cuprite
|
59
|
-
|
60
|
-
capybara_driver = Capybara::Cuprite::Driver.new(nil)
|
61
|
-
capybara_driver.instance_variable_set(:@capybara, browser)
|
62
|
-
|
63
|
-
session = Capybara::Session.new(:cuprite, nil)
|
64
|
-
session.instance_variable_set(:@driver, capybara_driver)
|
65
|
-
|
66
|
-
session
|
27
|
+
def live(instance)
|
28
|
+
success(url: instance.current_url,
|
29
|
+
body: instance.body,
|
30
|
+
status_code: instance.network.response.status,
|
31
|
+
headers: instance.network.response.headers)
|
67
32
|
end
|
68
33
|
end
|
69
34
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Networking
|
5
|
+
class HTTP
|
6
|
+
include Strategy
|
7
|
+
|
8
|
+
CONNECTION_NAME = "wayfarer"
|
9
|
+
|
10
|
+
def create
|
11
|
+
Net::HTTP::Persistent.new(name: CONNECTION_NAME).tap do |conn|
|
12
|
+
Wayfarer.config.network.http_headers.each do |key, val|
|
13
|
+
conn.override_headers[key] = val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy(instance)
|
19
|
+
instance.shutdown
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch(instance, url)
|
23
|
+
res = instance.request(URI(url))
|
24
|
+
|
25
|
+
return redirect(res["location"]) if res.is_a?(Net::HTTPRedirection)
|
26
|
+
|
27
|
+
success(url: url,
|
28
|
+
status_code: res.code.to_i,
|
29
|
+
body: res.body,
|
30
|
+
headers: res.to_hash.transform_values(&:first))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -6,28 +6,33 @@ module Wayfarer
|
|
6
6
|
include Singleton
|
7
7
|
extend Forwardable
|
8
8
|
|
9
|
+
cattr_accessor :registry, default: { http: HTTP,
|
10
|
+
ferrum: Ferrum,
|
11
|
+
selenium: Selenium,
|
12
|
+
capybara: Capybara }
|
13
|
+
|
9
14
|
attr_reader :pool
|
10
15
|
|
11
16
|
def initialize
|
12
|
-
@pool = ConnectionPool.new(size: Wayfarer.config.
|
13
|
-
timeout: Wayfarer.config.
|
14
|
-
&method(:
|
17
|
+
@pool = ConnectionPool.new(size: Wayfarer.config.network.pool_size,
|
18
|
+
timeout: Wayfarer.config.network.pool_timeout,
|
19
|
+
&method(:context))
|
15
20
|
end
|
16
21
|
|
17
22
|
delegate with: :pool
|
18
23
|
|
19
24
|
def free
|
20
|
-
pool.shutdown(&:
|
25
|
+
pool.shutdown(&:renew)
|
21
26
|
end
|
22
27
|
|
23
28
|
private
|
24
29
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def context
|
31
|
+
Wayfarer::Networking::Context.new(strategy)
|
32
|
+
end
|
33
|
+
|
34
|
+
def strategy
|
35
|
+
self.class.registry[Wayfarer.config.network.agent].new
|
31
36
|
end
|
32
37
|
end
|
33
38
|
end
|
@@ -3,67 +3,40 @@
|
|
3
3
|
module Wayfarer
|
4
4
|
module Networking
|
5
5
|
class Selenium
|
6
|
-
|
7
|
-
[] # TODO: Figure out when to renew
|
8
|
-
end
|
9
|
-
|
10
|
-
FAKE_STATUS_CODE = 200
|
11
|
-
FAKE_RESPONSE_HEADERS = {}.freeze
|
12
|
-
|
13
|
-
attr_reader :browser
|
6
|
+
include Strategy
|
14
7
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def fetch(url)
|
20
|
-
browser.navigate.to(url)
|
21
|
-
Result::Success.new(live(nil))
|
22
|
-
end
|
8
|
+
MOCK_STATUS_CODE = 200
|
9
|
+
MOCK_RESPONSE_HEADERS = {}.freeze
|
23
10
|
|
24
|
-
def
|
25
|
-
|
26
|
-
body: browser.page_source,
|
27
|
-
status_code: FAKE_STATUS_CODE,
|
28
|
-
headers: FAKE_RESPONSE_HEADERS)
|
11
|
+
def create
|
12
|
+
::Selenium::WebDriver.for(Wayfarer.config.selenium.driver, **options)
|
29
13
|
end
|
30
14
|
|
31
|
-
def
|
32
|
-
|
15
|
+
def destroy(instance)
|
16
|
+
instance.quit
|
33
17
|
end
|
34
18
|
|
35
|
-
def
|
36
|
-
|
37
|
-
@browser = instantiate_browser
|
19
|
+
def navigate(instance, url)
|
20
|
+
instance.navigate.to(url)
|
38
21
|
end
|
39
22
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def capybara
|
47
|
-
@capybara ||= instantiate_capybara_driver
|
23
|
+
def live(instance)
|
24
|
+
success(url: instance.current_url,
|
25
|
+
body: instance.page_source,
|
26
|
+
status_code: MOCK_STATUS_CODE,
|
27
|
+
headers: MOCK_RESPONSE_HEADERS)
|
48
28
|
end
|
49
29
|
|
50
30
|
private
|
51
31
|
|
52
|
-
def
|
53
|
-
|
32
|
+
def options
|
33
|
+
Wayfarer.config.selenium.options.merge(http_client: http_client)
|
54
34
|
end
|
55
35
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
capybara_driver = Capybara::Selenium::Driver.new(nil)
|
61
|
-
capybara_driver.instance_variable_set(:@capybara, browser)
|
62
|
-
|
63
|
-
session = Capybara::Session.new(:selenium, nil)
|
64
|
-
session.instance_variable_set(:@driver, capybara_driver)
|
65
|
-
|
66
|
-
session
|
36
|
+
def http_client
|
37
|
+
::Selenium::WebDriver::Remote::Http::Default.new.tap do |client|
|
38
|
+
client.read_timeout = Wayfarer.config.selenium.client_timeout
|
39
|
+
end
|
67
40
|
end
|
68
41
|
end
|
69
42
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Networking
|
5
|
+
module Strategy
|
6
|
+
def renew_on
|
7
|
+
[]
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch(instance, url)
|
11
|
+
navigate(instance, url)
|
12
|
+
live(instance)
|
13
|
+
end
|
14
|
+
|
15
|
+
def navigate(_instance, _url)
|
16
|
+
raise NoMethodError
|
17
|
+
end
|
18
|
+
|
19
|
+
def live(_instance); end
|
20
|
+
|
21
|
+
def create
|
22
|
+
raise NoMethodError
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy(_instance); end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def success(...)
|
30
|
+
Wayfarer::Networking::Result::Success.new(Wayfarer::Page.new(...))
|
31
|
+
end
|
32
|
+
|
33
|
+
def redirect(...)
|
34
|
+
Wayfarer::Networking::Result::Redirect.new(...)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/wayfarer/page.rb
CHANGED
@@ -4,11 +4,10 @@ module Wayfarer
|
|
4
4
|
class Page
|
5
5
|
attr_reader :url,
|
6
6
|
:status_code,
|
7
|
+
:body,
|
7
8
|
:headers
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(url:, status_code:, headers:, body:)
|
10
|
+
def initialize(url:, status_code:, body:, headers:)
|
12
11
|
@url = url
|
13
12
|
@status_code = status_code
|
14
13
|
@body = body
|
data/lib/wayfarer/redis/pool.rb
CHANGED
data/lib/wayfarer/routing/dsl.rb
CHANGED
@@ -4,40 +4,40 @@ module Wayfarer
|
|
4
4
|
module Routing
|
5
5
|
module DSL
|
6
6
|
def url(url, options = {}, &block)
|
7
|
-
add_child_route(
|
7
|
+
add_child_route(Matchers::URL.new(url), path_offset, options, &block)
|
8
8
|
end
|
9
9
|
|
10
10
|
def host(host, options = {}, &block)
|
11
|
-
add_child_route(
|
11
|
+
add_child_route(Matchers::Host.new(host), path_offset, options, &block)
|
12
12
|
end
|
13
13
|
|
14
14
|
def path(path, options = {}, &block)
|
15
15
|
offset = File.join(path_offset, path)
|
16
16
|
add_child_route(nil, offset, options, &block).tap do |route|
|
17
|
-
route.matcher =
|
17
|
+
route.matcher = Matchers::Path.new(offset, route)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def query(fields, options = {}, &block)
|
22
|
-
add_child_route(
|
22
|
+
add_child_route(Matchers::Query.new(fields), path_offset, options, &block)
|
23
23
|
end
|
24
24
|
|
25
25
|
def scheme(scheme, options = {}, &block)
|
26
|
-
add_child_route(
|
26
|
+
add_child_route(Matchers::Scheme.new(scheme), path_offset, options, &block)
|
27
27
|
end
|
28
28
|
|
29
29
|
def suffix(suffix, options = {}, &block)
|
30
|
-
add_child_route(
|
30
|
+
add_child_route(Matchers::Suffix.new(suffix), path_offset, options, &block)
|
31
31
|
end
|
32
32
|
|
33
33
|
def to(action, options = {}, &block)
|
34
|
-
add_child_route(
|
34
|
+
add_child_route(Matchers::Custom.new { true }, path_offset, TargetRoute, options, &block).tap do |route|
|
35
35
|
route.action = action
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def custom(delegate, options = {}, &block)
|
40
|
-
add_child_route(
|
40
|
+
add_child_route(Matchers::Custom.new(delegate), path_offset, options, &block)
|
41
41
|
end
|
42
42
|
|
43
43
|
private
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Routing
|
5
|
+
module Matchers
|
6
|
+
class Custom
|
7
|
+
attr_reader :delegate
|
8
|
+
|
9
|
+
def initialize(delegate = proc)
|
10
|
+
@delegate = delegate
|
11
|
+
end
|
12
|
+
|
13
|
+
def match(url)
|
14
|
+
!!delegate.call(url)
|
15
|
+
end
|
16
|
+
|
17
|
+
def params(_)
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Routing
|
5
|
+
module Matchers
|
6
|
+
Host = Struct.new(:host) do
|
7
|
+
# rubocop:disable Style/CaseEquality
|
8
|
+
def match(url)
|
9
|
+
host === url.host
|
10
|
+
end
|
11
|
+
# rubocop:enable Style/CaseEquality
|
12
|
+
|
13
|
+
def params(_)
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Routing
|
5
|
+
module Matchers
|
6
|
+
class Path
|
7
|
+
attr_reader :path,
|
8
|
+
:route,
|
9
|
+
:peeking,
|
10
|
+
:matcher
|
11
|
+
|
12
|
+
def initialize(path, route)
|
13
|
+
@path = path
|
14
|
+
@route = route
|
15
|
+
@peeking = false
|
16
|
+
@matcher = Mustermann.new(path, type: "sinatra")
|
17
|
+
end
|
18
|
+
|
19
|
+
def match(url)
|
20
|
+
route.accept(self)
|
21
|
+
|
22
|
+
# If the route's branch contains other path matchers in child routes,
|
23
|
+
# match the beginning of the path (peeking), instead of the whole path.
|
24
|
+
!!(if peeking
|
25
|
+
matcher.peek(url.path)
|
26
|
+
else
|
27
|
+
matcher.match(url.path)
|
28
|
+
end)
|
29
|
+
end
|
30
|
+
|
31
|
+
def params(url)
|
32
|
+
return {} unless match(url)
|
33
|
+
|
34
|
+
matcher.params(url.path) || {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit(route)
|
38
|
+
return true if route == self.route
|
39
|
+
|
40
|
+
return unless route.matcher.is_a?(self.class)
|
41
|
+
|
42
|
+
@peeking = true
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wayfarer
|
4
|
+
module Routing
|
5
|
+
module Matchers
|
6
|
+
Query = Struct.new(:fields) do
|
7
|
+
def match(url)
|
8
|
+
query = url.query
|
9
|
+
|
10
|
+
# CGI::parse throws a NoMethodError if the query is an empty string
|
11
|
+
return false if query.nil? || query.empty?
|
12
|
+
|
13
|
+
CGI.parse(query).none? { |field, vals| violates?(field, vals) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def params(url)
|
17
|
+
return {} unless match(url)
|
18
|
+
|
19
|
+
CGI.parse(url.query)
|
20
|
+
.select { |(k, _)| fields.keys.include?(k.to_sym) }
|
21
|
+
.transform_values(&:last)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# rubocop:disable Lint/AssignmentInCondition
|
27
|
+
def violates?(field, vals)
|
28
|
+
return false unless constraint = fields[field.to_sym]
|
29
|
+
|
30
|
+
violates_constraint?(constraint, vals)
|
31
|
+
end
|
32
|
+
# rubocop:enable Lint/AssignmentInCondition
|
33
|
+
|
34
|
+
def violates_constraint?(constraint, vals)
|
35
|
+
case constraint
|
36
|
+
when String then violates_string?(constraint, vals)
|
37
|
+
when Integer then violates_integer?(constraint, vals)
|
38
|
+
when Regexp then violates_regexp?(constraint, vals)
|
39
|
+
when Range then violates_range?(constraint, vals)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def violates_string?(str, vals)
|
44
|
+
vals.none? { |val| str == val }
|
45
|
+
end
|
46
|
+
|
47
|
+
def violates_integer?(int, vals)
|
48
|
+
vals.none? { |val| int == Integer(val) }
|
49
|
+
rescue ArgumentError
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def violates_regexp?(regexp, vals)
|
54
|
+
vals.none? { |val| regexp.match(val) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def violates_range?(range, vals)
|
58
|
+
vals.none? { |val| range.include?(val.to_i) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|