landline 0.9.2

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/HACKING.md +30 -0
  3. data/LAYOUT.md +59 -0
  4. data/LICENSE.md +660 -0
  5. data/README.md +159 -0
  6. data/lib/landline/dsl/constructors_path.rb +107 -0
  7. data/lib/landline/dsl/constructors_probe.rb +28 -0
  8. data/lib/landline/dsl/methods_common.rb +28 -0
  9. data/lib/landline/dsl/methods_path.rb +75 -0
  10. data/lib/landline/dsl/methods_probe.rb +129 -0
  11. data/lib/landline/dsl/methods_template.rb +16 -0
  12. data/lib/landline/node.rb +87 -0
  13. data/lib/landline/path.rb +157 -0
  14. data/lib/landline/pattern_matching/glob.rb +168 -0
  15. data/lib/landline/pattern_matching/rematch.rb +49 -0
  16. data/lib/landline/pattern_matching/util.rb +15 -0
  17. data/lib/landline/pattern_matching.rb +75 -0
  18. data/lib/landline/probe/handler.rb +56 -0
  19. data/lib/landline/probe/http_method.rb +74 -0
  20. data/lib/landline/probe/serve_handler.rb +39 -0
  21. data/lib/landline/probe.rb +62 -0
  22. data/lib/landline/request.rb +135 -0
  23. data/lib/landline/response.rb +140 -0
  24. data/lib/landline/server.rb +49 -0
  25. data/lib/landline/template/erb.rb +27 -0
  26. data/lib/landline/template/erubi.rb +36 -0
  27. data/lib/landline/template.rb +95 -0
  28. data/lib/landline/util/cookie.rb +150 -0
  29. data/lib/landline/util/errors.rb +11 -0
  30. data/lib/landline/util/html.rb +119 -0
  31. data/lib/landline/util/lookup.rb +37 -0
  32. data/lib/landline/util/mime.rb +1276 -0
  33. data/lib/landline/util/multipart.rb +175 -0
  34. data/lib/landline/util/parsesorting.rb +37 -0
  35. data/lib/landline/util/parseutils.rb +111 -0
  36. data/lib/landline/util/query.rb +66 -0
  37. data/lib/landline.rb +20 -0
  38. metadata +85 -0
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'pattern_matching'
4
+ require_relative 'node'
5
+ require_relative 'dsl/constructors_path'
6
+ require_relative 'dsl/methods_path'
7
+ require_relative 'dsl/methods_common'
8
+ require_relative 'util/lookup'
9
+
10
+ module Landline
11
+ # Execution context for filters and preprocessors.
12
+ class ProcessorContext
13
+ include Landline::DSL::CommonMethods
14
+
15
+ def initialize(path)
16
+ @origin = path
17
+ end
18
+ end
19
+
20
+ # Execution context for path setup block.
21
+ class PathContext
22
+ include Landline::DSL::PathConstructors
23
+ include Landline::DSL::PathMethods
24
+
25
+ def initialize(path)
26
+ @origin = path
27
+ end
28
+ end
29
+
30
+ # Primary building block of request navigation.
31
+ class Path < Landline::Node
32
+ ProcContext = Landline::ProcessorContext
33
+ Context = Landline::PathContext
34
+
35
+ # @param path [Object] Object to generate {Landline::Pattern} from
36
+ # @param parent [Landline::Node] Parent object to inherit properties to
37
+ # @param setup [#call] Setup block
38
+ def initialize(path, parent:, **params, &setup)
39
+ super(path, parent: parent, **params)
40
+ # Child nodes array
41
+ @children = []
42
+ # Arrays of preprocessors, postprocessors and filters
43
+ @preprocessors = []
44
+ @postprocessors = []
45
+ @filters = []
46
+ # Contexts setup
47
+ context = self.class::Context.new(self)
48
+ context.instance_exec(&setup)
49
+ @proccontext = self.class::ProcContext.new(self)
50
+ end
51
+
52
+ # Method callback on successful request navigation.
53
+ # Finds the next appropriate path to go to.
54
+ # @return [Boolean] true if further navigation will be done
55
+ # @raise [UncaughtThrowError] by default throws :response if no matches found.
56
+ def process(request)
57
+ return false unless run_filters(request)
58
+
59
+ run_preprocessors(request)
60
+ enqueue_postprocessors(request)
61
+ @children.each do |x|
62
+ if (value = x.go(request))
63
+ return value
64
+ end
65
+ end
66
+ value = index(request)
67
+ return value if value
68
+
69
+ _die(404)
70
+ rescue StandardError => e
71
+ _die(500, backtrace: [e.to_s] + e.backtrace)
72
+ end
73
+
74
+ # Add a preprocessor to the path.
75
+ # Does not modify path execution.
76
+ # @param block [#call]
77
+ # @yieldparam request [Landline::Request]
78
+ def preprocess(&block)
79
+ @preprocessors.append(block)
80
+ end
81
+
82
+ # Add a postprocessor to the path.
83
+ # @param block [#call]
84
+ # @yieldparam request [Landline::Request]
85
+ # @yieldparam response [Landline::Response]
86
+ def postprocess(&block)
87
+ @postprocessors.append(block)
88
+ end
89
+
90
+ # Add a filter to the path.
91
+ # Blocks path access if a filter returns false.
92
+ # @param block [#call]
93
+ # @yieldparam request [Landline::Request]
94
+ def filter(&block)
95
+ @filters.append(block)
96
+ end
97
+
98
+ attr_reader :children, :properties
99
+
100
+ private
101
+
102
+ # Sequentially run through all filters and drop request if one is false
103
+ # @param request [Landline::Request]
104
+ # @return [Boolean] true if request passed all filters
105
+ def run_filters(request)
106
+ @filters.each do |filter|
107
+ return false unless @proccontext.instance_exec(request, &filter)
108
+ end
109
+ true
110
+ end
111
+
112
+ # Sequentially run all preprocessors on a request
113
+ # @param request [Landline::Request]
114
+ def run_preprocessors(request)
115
+ @preprocessors.each do |preproc|
116
+ @proccontext.instance_exec(request, &preproc)
117
+ end
118
+ end
119
+
120
+ # Append postprocessors to request
121
+ # @param request [Landline::Request]
122
+ def enqueue_postprocessors(request)
123
+ request.postprocessors.append(*@postprocessors)
124
+ end
125
+
126
+ # Try to perform indexing on the path if possible
127
+ # @param request [Landline::Request]
128
+ # @return [Boolean] true if indexing succeeded
129
+ def index(request)
130
+ return false unless request.path.match?(/^\/?$/)
131
+
132
+ @properties["index"].each do |index|
133
+ request.path = index
134
+ @children.each do |x|
135
+ if (value = x.go(request))
136
+ return value
137
+ end
138
+ end
139
+ end
140
+ false
141
+ end
142
+
143
+ # Handle an errorcode
144
+ # @param errorcode [Integer]
145
+ # @param backtrace [Array(String), nil]
146
+ # @raise [UncaughtThrowError] throws :finish to stop processing
147
+ def _die(errorcode, backtrace: nil)
148
+ throw :finish, [errorcode].append(
149
+ *(@properties["handle.#{errorcode}"] or
150
+ @properties["handle.default"]).call(
151
+ errorcode,
152
+ backtrace: backtrace
153
+ )
154
+ )
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Landline
4
+ module PatternMatching
5
+ # someday i've heard this quote:
6
+ # "if you love regexp so much why don't you marry the damn thing?"
7
+ # and so i did, as you can see here.
8
+
9
+ # Implements glob-like pattern matching.
10
+ # Exact specifications for globbing rules:
11
+ #
12
+ # "/"
13
+ # - act as directory separators
14
+ # - multiple slashes (i.e. "///") are the same as one slash ("/")
15
+ # - slashes are stripped at start and end of an expression or path
16
+ # - slashes are not matched by anything but the globstar ("**")
17
+ #
18
+ # "*" ( regexp: /([^/]*)/ )
19
+ # - matches from 0 to any number of characters
20
+ # - does not match nothing if placed between two slashes (i.e "/*/")
21
+ # - result is captured in an array
22
+ # - stops at slashes
23
+ # - greedy (matches as much as possible)
24
+ #
25
+ # "**" ( regexp: /(.*)/ )
26
+ # - matches any number of characters
27
+ # - matches slashes ("/")
28
+ # - result is captured in an array
29
+ # - does not stop at slashes
30
+ # - greedy (matches as much as possible)
31
+ #
32
+ # "[...]+" ( regexp: itself, ! and ^ at the start are interchangeable )
33
+ # - acts like a regexp range
34
+ # - matches any characters, including slashes if specified
35
+ # - matches any number of characters
36
+ # - valid ways to specify a range: [A-z], [a-z], [9-z] (ascii order)
37
+ # - ! or ^ at the start invert meaning (any character not in range)
38
+ # - result is captured in an array
39
+ #
40
+ # ":name" ( regexp: acts like a named group for /[^/]*/ )
41
+ # - acts like * as defined above
42
+ # - result is captured in a hash with "name" as key
43
+ # - name allows alphanumeric characters and underscores
44
+ # - ignored unless placed between two slashes
45
+ #
46
+ # "(name|name2|...)" ( regexp: itself )
47
+ # - matches any of (full keywords) name, name2, ...
48
+ # - result is captured in an array
49
+ # - each name may include only these groups of characters:
50
+ # - alphanumeric
51
+ # - slashes
52
+ # - underscores
53
+ # - dashes
54
+ class Glob
55
+ # @param input [String] Glob pattern
56
+ def initialize(pattern)
57
+ pattern = Landline::PatternMatching.canonicalize(pattern)
58
+ pieces = pattern.split(TOKENS)
59
+ # @type [Array<String,Integer>]
60
+ @index = build_index(pieces)
61
+ # @type [Regexp]
62
+ @glob = build_regexp(pieces)
63
+ end
64
+
65
+ # Match the string and assign matches to parameters.
66
+ # Returns:
67
+ #
68
+ # - Unmatched part of a string
69
+ # - Unnamed parameters
70
+ # - Named parameters
71
+ # @param input [String] String to match
72
+ # @return [Array(String,Array,Hash)]
73
+ # @return [FalseClass] if input doesn't match glob
74
+ def match(input)
75
+ input = Landline::PatternMatching.canonicalize(input)
76
+ result = input.match(@glob)
77
+ return false unless result
78
+
79
+ input = result.post_match
80
+ named_params, params = assign_by_index(@index, result.captures)
81
+ [input, params, named_params]
82
+ end
83
+
84
+ # Test if a string can be matched.
85
+ # Lighter version of match that doesn't assign any variables.
86
+ # @param input [String] String to match
87
+ # @return [Boolean]
88
+ def match?(input)
89
+ input = Landline::PatternMatching.canonicalize(input)
90
+ input.match? @glob
91
+ end
92
+
93
+ # Test if input is convertible to a Glob and if it should be converted
94
+ # @param input
95
+ # @return [Boolean] Input can be safely converted to Glob
96
+ def self.can_convert?(input)
97
+ input.is_a? String and
98
+ input.match?(TOKENS)
99
+ end
100
+
101
+ private
102
+
103
+ # i shall name thee Norxondor Glorbulon
104
+
105
+ # Regexp pattern to match glob tokens
106
+ TOKENS = /
107
+ ( # Glob-specific tokens
108
+ (?<=(?:\/|^))\*\*(?:\/|$) | # Freestanding globstar
109
+ \* | # Regular glob
110
+ \[!?\w-\w\]\+ | # Character group
111
+ (?<=\/):[\w_]+(?=(?:\/|$)) | # Named glob
112
+ \([\w\/|_-]+\) # Alternator group
113
+ )
114
+ /x.freeze
115
+
116
+ # Build an index for separating normal matches from named matches
117
+ # @param pieces [Array(String)] Glob pattern after being splitted
118
+ # @return [Array(String,Integer)] Index array for assign_by_index
119
+ def build_index(pieces)
120
+ count = -1
121
+ index = []
122
+ pieces.each do |x|
123
+ if (name = x.match(/^:([\w_]+)$/))
124
+ index.append(name[1])
125
+ elsif x.match?(TOKENS)
126
+ index.append(count += 1)
127
+ end
128
+ end
129
+ index
130
+ end
131
+
132
+ # Assign values from match.captures to named and numbered groups
133
+ # @param index [Array(String,Integer)] Index array
134
+ # @param params [Array] Unnamed captures from a String.match
135
+ # @return [Array(Hash,Array)]
136
+ def assign_by_index(index, params)
137
+ named_params = {}
138
+ new_params = []
139
+ params.each_with_index do |x, k|
140
+ # FIXME: this is a hack for globstar implementation at line 152
141
+ x = (x.nil? ? "" : x)
142
+ if index[k].is_a? String
143
+ named_params[index[k].to_sym] = x
144
+ else
145
+ new_params[index[k]] = x
146
+ end
147
+ end
148
+ [named_params, new_params]
149
+ end
150
+
151
+ # Create a Regexp from a glob pattern according to defined syntax
152
+ # @param tokens [Array(String)] Split array of glob tokens
153
+ # @return [Regexp]
154
+ def build_regexp(tokens)
155
+ Regexp.new(tokens.map do |filter|
156
+ case filter
157
+ when /(\/|^)\*\*(\/|$)/ then "(?:(.*)/|)" # FIXME: this may return nil
158
+ when "*" then "([^/]*)"
159
+ when /^\([\w\/|_-]+\)$/ then filter.sub('-', '\\-')
160
+ when /^\[!?\w-\w\]\+$/ then filter.sub('!', '^')
161
+ when /^:[\w_]+$/ then "([^/]*)"
162
+ else filter.gsub(/[\^$.|+(){}]/, '\\\\\\0')
163
+ end
164
+ end.join("").prepend("^/?"))
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Landline
4
+ module PatternMatching
5
+ # Regexp pattern matching wrapper.
6
+ # Following principles apply to the wrapper:
7
+ #
8
+ # - Regexp input is canonicalized using Landline::PatternMatching.canonicalize
9
+ # - Matched content and anything before it is consumed in #match output
10
+ #
11
+ # @note If you are planning to write your own pattern, this is the easiest one to read.
12
+ class ReMatch
13
+ def initialize(pattern)
14
+ @glob = pattern
15
+ end
16
+
17
+ # Match the string and assign matches to parameters.
18
+ # Returns:
19
+ #
20
+ # - Unmatched part of a string
21
+ # - Unnamed parameters
22
+ # - Named parameters
23
+ # @param input [String] String to match
24
+ # @return [Array(String,Array,Hash)]
25
+ # @return [FalseClass] if input doesn't match regexp
26
+ def match(input)
27
+ result = Landline::PatternMatching.canonicalize(input).match(@glob)
28
+ return false unless result
29
+
30
+ [result.post_match, result.captures, result.named_captures]
31
+ end
32
+
33
+ # Test if a string can be matched.
34
+ # Lighter version of match that doesn't assign any variables.
35
+ # @param input [String] String to match
36
+ # @return [Boolean]
37
+ def match?(input)
38
+ Landline::PatternMatching.canonicalize(input).match? @glob
39
+ end
40
+
41
+ # Test if input is convertible and if it should be converted.
42
+ # @param input
43
+ # @return [Boolean] Input can be safely converted to Glob
44
+ def self.can_convert?(string)
45
+ string.is_a? Regexp
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Landline
4
+ module PatternMatching
5
+ # Strips extra slashes from a string.
6
+ # (including slashes at the start and end of the string)
7
+ # @param string [String]
8
+ # @return [String]
9
+ def self.canonicalize(string)
10
+ string.gsub(/\/+/, "/")
11
+ .delete_prefix("/")
12
+ .delete_suffix("/")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'pattern_matching/util'
4
+ require_relative 'pattern_matching/glob'
5
+ require_relative 'pattern_matching/rematch'
6
+
7
+ module Landline
8
+ # Utility functions and pattern-generator classes.
9
+ # Used primarily to create patterns for path definitions.
10
+ module PatternMatching end
11
+
12
+ # Delegate class for all available patterns.
13
+ # Picks appropriate pattern based on contents.
14
+ class Pattern
15
+ # @param pattern [Object] object to generate pattern from
16
+ def initialize(pattern)
17
+ @pattern = patternify(pattern)
18
+ @static = @pattern.is_a? String
19
+ end
20
+
21
+ # Checks if pattern object is static (is a simple String pattern).
22
+ def static?
23
+ @static
24
+ end
25
+
26
+ # Match the string and assign matches to parameters.
27
+ # Returns:
28
+ #
29
+ # - Unmatched part of a string
30
+ # - Unnamed parameters
31
+ # - Named parameters
32
+ # @param input [String] String to match
33
+ # @return [Array(String,Array,Hash)]
34
+ # @return [FalseClass] if input doesn't match pattern
35
+ def match(input)
36
+ if @pattern.is_a? String
37
+ input = Landline::PatternMatching.canonicalize(input)
38
+ if input.start_with?(@pattern)
39
+ [input.delete_prefix(@pattern), [], {}]
40
+ else
41
+ false
42
+ end
43
+ else
44
+ @pattern.match(input)
45
+ end
46
+ end
47
+
48
+ # Test if a string can be matched.
49
+ # Lighter version of match that doesn't assign any variables.
50
+ # @param input [String] String to match
51
+ # @return [Boolean]
52
+ def match?(input)
53
+ if @pattern.is_a? String
54
+ Landline::PatternMatching.canonicalize(input).start_with? @pattern
55
+ else
56
+ @pattern.match?(input)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def patternify(pattern)
63
+ classdomain = Landline::PatternMatching
64
+ classdomain.constants
65
+ .filter { |x| classdomain.const_get(x).is_a? Class }
66
+ .map { |x| classdomain.const_get(x) }
67
+ .each do |pattern_generator|
68
+ if pattern_generator.can_convert? pattern
69
+ return pattern_generator.new(pattern)
70
+ end
71
+ end
72
+ Landline::PatternMatching.canonicalize(pattern)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../probe"
4
+
5
+ module Landline
6
+ module Handlers
7
+ # Probe that executes a callback on request
8
+ class Handler < Landline::Probe
9
+ # @param path [Object]
10
+ # @param parent [Landline::Node]
11
+ # @param exec [#call]
12
+ def initialize(path, **args, &exec)
13
+ super(path, **args)
14
+ @callback = exec
15
+ @context = Landline::ProbeContext.new(self)
16
+ @response = nil
17
+ end
18
+
19
+ attr_accessor :response
20
+ attr_reader :request
21
+
22
+ # Method callback on successful request navigation.
23
+ # Runs block supplied with object initialization.
24
+ # Request's #splat and #param are passed to block.
25
+ #
26
+ # Callback's returned should be one of viable responses:
27
+ #
28
+ # - {Landline::Response} object
29
+ # - An array that matches Rack return form
30
+ # - An array that matches old (Rack 2.x) return form
31
+ # - A string (returned as HTML with code 200)
32
+ # - false (bounces the request to next handler)
33
+ # @param request [Landline::Request]
34
+ # @return [Boolean] true if further navigation is possible
35
+ # @raise [UncaughtThrowError] may raise if die() is called.
36
+ def process(request)
37
+ @response = nil
38
+ return reject(request) unless request.path.match?(/^\/?$/)
39
+
40
+ @request = request
41
+ response = catch(:break) do
42
+ @context.instance_exec(*request.splat,
43
+ **request.param,
44
+ &@callback)
45
+ end
46
+ return false unless response
47
+
48
+ if @response and [String, File, IO].include? response.class
49
+ @response.body = response
50
+ throw :finish, @response
51
+ end
52
+ throw :finish, response
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../probe"
4
+
5
+ module Landline
6
+ module Handlers
7
+ # Probe that executes callback on a GET
8
+ class GET < Landline::Handlers::Handler
9
+ METHOD = "GET"
10
+
11
+ # Method callback on successful request navigation.
12
+ # Runs block supplied with object initialization.
13
+ # Request's #splat and #param are passed to block.
14
+ #
15
+ # Callback's returned should be one of viable responses:
16
+ #
17
+ # - {Landline::Response} object
18
+ # - An array that matches Rack return form
19
+ # - An array that matches old (Rack 2.x) return form
20
+ # - A string (returned as HTML with code 200)
21
+ # - false (bounces the request to next handler)
22
+ # @param request [Landline::Request]
23
+ # @return [Boolean] true if further navigation is possible
24
+ # @raise [UncaughtThrowError] may raise if die() is called.
25
+ def process(request)
26
+ unless request.request_method.casecmp(self.class::METHOD).zero?
27
+ return false
28
+ end
29
+
30
+ super(request)
31
+ end
32
+ end
33
+
34
+ # Probe that executes callback on a POST
35
+ class POST < GET
36
+ METHOD = "POST"
37
+ end
38
+
39
+ # Probe that executes callback on a HEAD
40
+ class HEAD < GET
41
+ METHOD = "HEAD"
42
+ end
43
+
44
+ # Probe that executes callback on a PUT
45
+ class PUT < GET
46
+ METHOD = "PUT"
47
+ end
48
+
49
+ # Probe that executes callback on a DELETE
50
+ class DELETE < GET
51
+ METHOD = "DELETE"
52
+ end
53
+
54
+ # Probe that executes callback on a CONNECT
55
+ class CONNECT < GET
56
+ METHOD = "CONNECT"
57
+ end
58
+
59
+ # Probe that executes callback on a OPTIONS
60
+ class OPTIONS < GET
61
+ METHOD = "OPTIONS"
62
+ end
63
+
64
+ # Probe that executes callback on a TRACE
65
+ class TRACE < GET
66
+ METHOD = "TRACE"
67
+ end
68
+
69
+ # Probe that executes callback on a PATCH
70
+ class PATCH < GET
71
+ METHOD = "PATCH"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../probe"
4
+ require_relative "../util/mime"
5
+
6
+ module Landline
7
+ module Handlers
8
+ # Probe that sends files from a location
9
+ class Serve < Landline::Probe
10
+ # @param path [Object]
11
+ # @param parent [Landline::Node]
12
+ # @param exec [#call]
13
+ def initialize(path, parent:)
14
+ super(path, parent: parent, filepath: true)
15
+ end
16
+
17
+ attr_accessor :response
18
+
19
+ # Method callback on successful request navigation.
20
+ # Tries to serve files matched by handler
21
+ # @param request [Landline::Request]
22
+ # @return [Boolean, Array] true if file was found
23
+ def process(request)
24
+ path = File.expand_path(request.filepath)
25
+ return unless path.start_with? @properties["path"]
26
+
27
+ filepath = path.delete_suffix("/")
28
+
29
+ [200,
30
+ {
31
+ "content-type" => Landline::MIME.get_mime_type(filepath)
32
+ },
33
+ File.open(filepath)]
34
+ rescue StandardError
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'util/lookup'
5
+ require_relative 'dsl/methods_probe'
6
+ require_relative 'dsl/methods_common'
7
+ require_relative 'dsl/constructors_probe'
8
+ require 'pp'
9
+
10
+ module Landline
11
+ # All subclasses of Landline::Probe that respond to requests
12
+ module Handlers
13
+ autoload :Handler, "landline/probe/handler"
14
+ autoload :GET, "landline/probe/http_method"
15
+ autoload :POST, "landline/probe/http_method"
16
+ autoload :HEAD, "landline/probe/http_method"
17
+ autoload :PUT, "landline/probe/http_method"
18
+ autoload :DELETE, "landline/probe/http_method"
19
+ autoload :CONNECT, "landline/probe/http_method"
20
+ autoload :OPTIONS, "landline/probe/http_method"
21
+ autoload :TRACE, "landline/probe/http_method"
22
+ autoload :PATCH, "landline/probe/http_method"
23
+ autoload :Serve, "landline/probe/serve_handler"
24
+ end
25
+
26
+ # Context that provides execution context for Probes.
27
+ class ProbeContext
28
+ include Landline::DSL::ProbeConstructors
29
+ include Landline::DSL::ProbeMethods
30
+ include Landline::DSL::CommonMethods
31
+
32
+ def initialize(origin)
33
+ @origin = origin
34
+ end
35
+ end
36
+
37
+ # Test probe. Also base for all "reactive" nodes.
38
+ class Probe < Landline::Node
39
+ # @param path [Object]
40
+ # @param parent [Landline::Node]
41
+ def initialize(path, parent:, **params)
42
+ super(path, parent: parent, **params)
43
+ end
44
+
45
+ attr_reader :properties
46
+
47
+ # Method callback on successful request navigation.
48
+ # Throws an error upon reaching the path.
49
+ # This behaviour should only be used internally.
50
+ # @param request [Landline::Request]
51
+ # @return [Boolean] true if further navigation is possible
52
+ # @raise [StandardError]
53
+ def process(request)
54
+ return reject(request) unless request.path.match?(/^\/?$/)
55
+
56
+ raise StandardError, <<~STREND
57
+ probe reached #{request.splat.inspect}, #{request.param.inspect}
58
+ #{request.pretty_inspect}
59
+ STREND
60
+ end
61
+ end
62
+ end