raindeer 0.0.0 → 0.1.0

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: 7f7ae1bba0016a40407b2cf3c2aed11c488804e91cc72e31c99196e149b4441f
4
- data.tar.gz: 21a6a788f1644a7750a015ae11b8a7139e0fe59c6f8020450255dc6030aa8d56
3
+ metadata.gz: 469efa25bc152127c137e44e3abff2d3e91aa79e17d9aac36bd18bb9a797f33b
4
+ data.tar.gz: 58c0b86b6bed031341331505716c7083715fa10569e4b5739ed3cb0a2a4b7209
5
5
  SHA512:
6
- metadata.gz: 98209c5df7cb9efc6bdb71e692630ced455e862dfa09c9191db122cedfc109b3289e7f193182ef7940775c7e4d9b479e9f62d0d5c869cf72b85949fb34246c5a
7
- data.tar.gz: c8bd4fba60835d9fbb4ee61142efda3cfcaa16d7a094cdb1e10b44d3ef3b94c274a1bff9878c639fad31c2d483986561a78c34660dcc7daefe8047da0f555e7e
6
+ metadata.gz: 6959daa057254a96fb30f2ace877d5dad8d94a6511f488efb6d62a871d29b459b3abf4537e005ac2bb6ca699541a54de943f7fe5226c6e2a7fe3a1cbde0f2239
7
+ data.tar.gz: e9b8aced4d8ec8ec77a6dbf11f19917bf5dd3db20151590d58d2f769b965d0e90fc3f455948e59841f284aa5fd07048bbf66c9e0127a7be52cfce6941538a559
data/lib/boot.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'low_dependency'
4
+ require 'low_loop'
5
+
6
+ require_relative 'router/router'
7
+
8
+ LowDependency.provide('low.loop') do
9
+ LowLoop.new
10
+ end
11
+
12
+ LowDependency.provide('rain.router') do
13
+ # TODO: Use "def method(dependency: Dependency)" in rain router's constructor when this feature is ready.
14
+ RainRouter.new(low_loop: Low::Providers['low.loop'])
15
+ end
16
+
17
+ require_relative 'system/system'
data/lib/raindeer.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'low_dependency'
4
+ require 'low_event'
5
+ require 'low_node'
6
+ require 'low_type'
7
+ require 'observers'
8
+
9
+ require_relative 'router/router'
10
+
11
+ class Raindeer
12
+ class << self
13
+ def router(&block)
14
+ Low::Providers['rain.router'].instance_eval(&block)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rain
4
+ class Route
5
+ include LowType
6
+
7
+ attr_reader :path, :verbs
8
+
9
+ def initialize(path: String, verbs: Array[String | nil] | [])
10
+ @path = path
11
+ @verbs = verbs
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'low_event'
4
+
5
+ module Rain
6
+ class RouteEvent < ::LowEvent
7
+ attr_reader :route, :params
8
+
9
+ def initialize(route:, action: :render, params: Hash | nil)
10
+ super(action:)
11
+
12
+ @route = route
13
+ @params = params
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'low_dependency'
4
+ require 'low_event'
5
+
6
+ require_relative 'route'
7
+ require_relative 'route_event'
8
+ require_relative 'trie'
9
+
10
+ module Rain
11
+ class Router
12
+ include LowType
13
+ include Observers
14
+
15
+ attr_reader :routes, :trie
16
+
17
+ def initialize(low_loop:)
18
+ @breadcrumbs = []
19
+ @routes = {}
20
+ @trie = Trie.new
21
+
22
+ observe low_loop
23
+ end
24
+
25
+ def route(path, verbs = [], &block)
26
+ @breadcrumbs << path
27
+ path = @breadcrumbs.join
28
+
29
+ route = Route.new(path:, verbs: [*verbs])
30
+ @routes[path] = route
31
+ @trie.merge(route:)
32
+
33
+ block.call if block_given?
34
+
35
+ @breadcrumbs.pop
36
+ end
37
+
38
+ def get(path = String, &block)
39
+ route(path, 'GET', &block)
40
+ end
41
+
42
+ def post(path = String, &block)
43
+ route(path, 'POST', &block)
44
+ end
45
+
46
+ def update(path = String, &block)
47
+ route(path, 'UPDATE', &block)
48
+ end
49
+
50
+ def delete(path = String, &block)
51
+ route(path, 'DELETE', &block)
52
+ end
53
+
54
+ # TODO: Define type: ::Low::Events::RequestEvent
55
+ # You can override any route/status simply by adding your own observer.
56
+ def handle(event:)
57
+ response_event = nil
58
+
59
+ @trie.match(path: event.request.path).each do |route_event|
60
+ response_event = trigger route_event.route.path, event: route_event
61
+ end
62
+
63
+ if response_event.nil?
64
+ status = LowType::Status[404]
65
+ response_event = trigger status, action: :render, event: Low::Events::StatusEvent.new(status:, request: event.request)
66
+ end
67
+
68
+ response_event
69
+ end
70
+ end
71
+ end
72
+
73
+ RainRouter = Rain::Router
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'route'
4
+ require_relative 'route_event'
5
+ require_relative 'trie_node'
6
+
7
+ module Rain
8
+ class Trie
9
+ include LowType
10
+
11
+ PARAM_DELIMITERS = ['/', ':'].freeze
12
+ ARG_DELIMITERS = ['/'].freeze
13
+
14
+ attr_reader :root_node
15
+
16
+ def initialize
17
+ @root_node = TrieNode.new
18
+ end
19
+
20
+ def root_path_node
21
+ @root_node.nodes['/']
22
+ end
23
+
24
+ def merge(route:, current_node: @root_node, current_index: 0)
25
+ path = route.path
26
+
27
+ while current_index < path.length
28
+ key = path[current_index]
29
+
30
+ if key == ':'
31
+ key, current_index = capture_param(current_index:, path:)
32
+ else
33
+ current_index += 1
34
+ end
35
+
36
+ current_node = current_node.upsert_child(key:)
37
+ end
38
+
39
+ current_node.route = route
40
+ end
41
+
42
+ def match(path:, current_node: @root_node, current_index: 0, params: {})
43
+ return [] if (key = path[current_index]).nil?
44
+
45
+ route_events = []
46
+
47
+ # Static request path segment.
48
+ if (child_node = current_node.child(key:))
49
+ route_events << route_event(next_index: current_index + 1, params:, path:, route: child_node.route) if child_node.route
50
+ route_events = [*route_events, *match(path:, current_node: child_node, current_index: current_index + 1, params:)]
51
+ end
52
+
53
+ # Dynamic request path segment.
54
+ current_node.params.each do |param|
55
+ child_node = current_node.child(key: param)
56
+
57
+ arg, next_index = capture_arg(arg_start_index: current_index, path:)
58
+ params[param.delete_prefix(':').to_sym] = arg
59
+
60
+ route_events << route_event(next_index:, params:, path:, route: child_node.route) if child_node.route
61
+ route_events = [*route_events, *match(path:, current_node: child_node, current_index: next_index, params:)]
62
+ end
63
+
64
+ route_events
65
+ end
66
+
67
+ private
68
+
69
+ # End nodes render events, mid nodes handle events.
70
+ def route_event(next_index:, params:, path:, route:)
71
+ action = path[next_index].nil? ? :render : :handle
72
+ RouteEvent.new(action:, route:, params:)
73
+ end
74
+
75
+ def capture_param(current_index:, path:)
76
+ current_index += 1
77
+ param = [':']
78
+
79
+ path[current_index...path.length].chars.each do |char|
80
+ break if PARAM_DELIMITERS.include?(char)
81
+
82
+ current_index += 1
83
+ param << char
84
+ end
85
+
86
+ [param.join, current_index]
87
+ end
88
+
89
+ def capture_arg(arg_start_index:, path:)
90
+ next_index = arg_start_index
91
+ arg = []
92
+
93
+ path[arg_start_index...path.length].chars.each do |char|
94
+ arg << char
95
+ next_index += 1
96
+ break if path[next_index].nil? || ARG_DELIMITERS.include?(path[next_index])
97
+ end
98
+
99
+ [arg.join, next_index]
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'route'
4
+
5
+ module Rain
6
+ class TrieNode
7
+ include LowType
8
+ using LowType::Syntax
9
+
10
+ # TODO: Represent empty hash/array return value as {}/[] rather than Hash/Array (https://github.com/low-rb/low_type/issues/16)
11
+ type_reader nodes: Hash[String => TrieNode] | Hash
12
+ type_reader params: Array[String] | Array
13
+ type_accessor route: Route | nil
14
+
15
+ def initialize
16
+ @nodes = {}
17
+ @params = []
18
+ @route = nil
19
+ end
20
+
21
+ def child(key: String)
22
+ @nodes[key]
23
+ end
24
+
25
+ def upsert_child(key: String)
26
+ @params << key if key.start_with?(':')
27
+ @nodes[key] || @nodes[key] = TrieNode.new
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PageNotFoundNode < LowNode
4
+ observe Status[404]
5
+
6
+ def render(event: StatusEvent)
7
+ <<~HTML
8
+ <div class="page-not-found">
9
+ <em>404</em>
10
+ </div>
11
+ HTML
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../raindeer'
4
+ require_relative 'system_node'
5
+
6
+ Raindeer.router do
7
+ get '/system'
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'page_not_found_node'
4
+
5
+ class SystemNode < LowNode
6
+ observe '/system'
7
+
8
+ def render(event: RouteEvent)
9
+ 'SYSTEM!'
10
+ end
11
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raindeer
4
- VERSION = '0.0.0'
4
+ VERSION = '0.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raindeer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -9,6 +9,76 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: low_dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: low_event
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: low_loop
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: low_node
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: low_state
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
12
82
  - !ruby/object:Gem::Dependency
13
83
  name: low_type
14
84
  requirement: !ruby/object:Gem::Requirement
@@ -23,6 +93,48 @@ dependencies:
23
93
  - - "~>"
24
94
  - !ruby/object:Gem::Version
25
95
  version: '1.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: antlers
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: expressions
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: observers
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
26
138
  description: A new Ruby framework. Coming soon...
27
139
  email:
28
140
  - maediprichard@gmail.com
@@ -30,6 +142,16 @@ executables: []
30
142
  extensions: []
31
143
  extra_rdoc_files: []
32
144
  files:
145
+ - lib/boot.rb
146
+ - lib/raindeer.rb
147
+ - lib/router/route.rb
148
+ - lib/router/route_event.rb
149
+ - lib/router/router.rb
150
+ - lib/router/trie.rb
151
+ - lib/router/trie_node.rb
152
+ - lib/system/page_not_found_node.rb
153
+ - lib/system/system.rb
154
+ - lib/system/system_node.rb
33
155
  - lib/version.rb
34
156
  homepage: https://codeberg.org/raindeer/raindeer
35
157
  licenses: []