wayfarer 0.4.0 → 0.4.1
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 +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
|