porous 0.1.0 → 0.2.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: 764d087181654089e95897a26692efd6bf2b3302f36a1917695c9e5e7ba3c088
4
- data.tar.gz: 7eb52c0975ce2611e1e0b57591cd0da848c639a0873785a3e6023e3506852a18
3
+ metadata.gz: 605209154f6a44bc2c7a84934bf661de4c721a2a5ed5f3b9c21556064ac1672e
4
+ data.tar.gz: 27eb154e0be5f0b524e723355c1544541b26207f36e49eb372cea7d06e24cdd5
5
5
  SHA512:
6
- metadata.gz: 5ca6f72a3d234ca0e368da80f7c4c839fd6d80b5577ef3eca7df9f2c0788bbb3891a099bb62568eaa3357f3940c8e6c6cd65f5dfd42d25713cb85ea812696df7
7
- data.tar.gz: e16772a071f6cf91395774bac8b01374173624f5fff38e97539fbe3b72c6586538b2f716a851093cb46ee9aaa583ef1e713ed2baae042f305812ff14afd067ab
6
+ metadata.gz: f4244bbc93b9c53b55e486d52420653ce8ffd5ee5e8d642b91e25aafc439ea2c48b5622c84167083b93c05ef424f5ea2b5964b7a4696665e92ffab68d13a43b9
7
+ data.tar.gz: 58e28a76335c29b19f57c8b68e9c1c6bd00136b753025b230bcabf4a041a872e0efe8a8ebf233a15c7249de018662f4de9c240b27c10701989faeb71231bc866
data/.rubocop.yml CHANGED
@@ -1,13 +1,32 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.0
3
+ NewCops: enable
3
4
 
4
5
  Style/StringLiterals:
5
6
  Enabled: true
6
- EnforcedStyle: double_quotes
7
+ EnforcedStyle: single_quotes
7
8
 
8
9
  Style/StringLiteralsInInterpolation:
9
10
  Enabled: true
10
11
  EnforcedStyle: double_quotes
11
12
 
13
+ Style/Documentation:
14
+ Enabled: false
15
+
16
+ Style/GlobalVars:
17
+ Enabled: false
18
+
12
19
  Layout/LineLength:
13
20
  Max: 120
21
+
22
+ Metrics/BlockLength:
23
+ AllowedMethods:
24
+ - render
25
+ Metrics/MethodLength:
26
+ Max: 15
27
+ AllowedMethods:
28
+ - render
29
+
30
+ require:
31
+ - rubocop-rake
32
+ - rubocop-rspec
data/CHANGELOG.md CHANGED
@@ -1,12 +1,21 @@
1
1
  ## [Planned]
2
2
 
3
- - Server-side hot reloading
4
- - Dynamic page metadata
5
- - Client-side component rendering
6
- - Client-side routing
7
- - Websockets support
3
+ - WebSockets support
4
+ - Production mode
5
+
6
+ ## [0.2.0] - 18 February 2024
7
+
8
+ - Server-side hot reloading (browser reloads on changes)
9
+ - Dynamic page metadata (title and description)
10
+ - Less noisy logging (silence logging for Rack::Static)
11
+ - Client-side component rendering (sans SVG support)
12
+ - Client-side routing / navigation
8
13
  - Client-side hot reloading
9
14
 
15
+ ## [0.1.1] - 17 February 2024
16
+
17
+ - Server-side reloading (server requests reflect changes)
18
+
10
19
  ## [0.1.0] - 15 February 2024
11
20
 
12
21
  - `porous server` to run apps
data/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # Porous
1
+ # 🧽 Porous
2
2
 
3
- Porous is a web engine that uses isomorphic Ruby components to build a Progressive Web App. Its use is analogous to a web framework, but the approach is entirely different.
3
+ Porous is a web engine that uses isomorphic Ruby components to build a Progressive Web App. Its use is analogous to a web framework, but the approach is entirely different. You write only
4
4
 
5
5
  This project is a work-in-progress and is not yet even in the Proof of Concept phase. However, if you are interested in a full-stack, everything included solution, that only requires you to use one language (that is arguably easy and enjoyable to write) then feel free to follow this project.
6
6
 
7
7
  The closest thing to this I could find was [Volt](https://github.com/voltrb/volt) or [Silica](https://github.com/youchan/silica), neither of which are active or match the overall development flow I'm looking for.
8
8
 
9
+ ## Current Features
10
+
11
+ - 🙅 No bundled runtime (only code unique to your app needs to be in your repository)
12
+ - 🖥️ Server-side rendering (server responds with the entire initial page populated for SEO)
13
+ - 💻 Client-side rendering (application bundle is served and interactions and subsequent pages are rendered client-side)
14
+ - 🌄 Serves static files (from `static` folder)
15
+ - 🔥 Hot reloading (via HTTP polling and browser refresh)
16
+
9
17
  ## Design
10
18
 
11
19
  Applications are composed of `Page`s which are in turn composed of `Component`s. Data is persisted as `Entity`s in configurable store options (memory, disk, database). Client-server communication occurs as `Event`s over WebSockets.
@@ -16,7 +24,7 @@ A page is conceptually similar to what would be rendered when visiting a specifi
16
24
 
17
25
  ### Components
18
26
 
19
- A component is any composable unit of code responsible for rendering markup, potentially based on some state. This is somewhat equivalent to Web Components, in that it can also have some behaviour attached. But it can also simply be based to remove code duplication. Essentially any markup that has behaviour attached or would otherwise create code duplication should probably be in a Components.
27
+ A component is any composable unit of code responsible for rendering markup, potentially based on some state. This is somewhat equivalent to Web Components, in that it can also have some behaviour attached. But it can also simply be used to remove code duplication. Essentially any markup that has behaviour attached or would otherwise create code duplication should probably be in Components.
20
28
 
21
29
  ### Entities
22
30
 
@@ -36,11 +44,13 @@ Porous is not a framework. You don't build an application with it as a dependenc
36
44
 
37
45
  Porous is still pre-alpha and so is not ready for usage yet, but the general idea is that you would define your application's entities, pages, components and events in Ruby scripts structured in a specific way. Then you would simply run `porous` while pointing it to that folder and it will spin up a Rack-compatible web server for you to use.
38
46
 
39
- To start a new Porous project simply `gem install porous` using whichever Ruby environment you want to use (Ruby 3.0 minimum). Then change to that directory and run:
47
+ > ⚠️ Expect any and all APIs to change radically until version 1.0! Hence why it won't be documented or properly tested until things settle to a more stable state.
48
+
49
+ To start a new Porous project simply `gem install porous` using whichever Ruby environment you want to use (Ruby 3.0+). Then change to that directory and run:
40
50
 
41
51
  $ porous server
42
52
 
43
- By default Porous will run at `loclahost:9292`. Now you can edit `pages/home.rb` or add more pages. Finally restart the server and refresh the page. Hot-reloading will be coming later.
53
+ By default Porous will run at `localhost:9292`. Now you can edit `pages/home.rb` or add more pages. Files you modify will be hot-reloaded so you can simply open the page in your browser and edit the file. Hot-reloading will be improved once WebSockets support is implemented.
44
54
 
45
55
  ### Running examples
46
56
 
@@ -66,4 +76,4 @@ Everyone interacting in the Porous project's codebases, issue trackers, chat roo
66
76
 
67
77
  ## Acknowledgements
68
78
 
69
- The work done by Michał Kalbarczyk ([fazibear](https://github.com/fazibear)) on [Inesita](https://github.com/inesita-rb/inesita) and his [VirtualDOM wrapper](https://github.com/fazibear/opal-virtual-dom) which served as the starting point for my implementation of Porous.
79
+ I'd like to thank Michał Kalbarczyk ([fazibear](https://github.com/fazibear)) for his work done on [Inesita](https://github.com/inesita-rb/inesita) and his [VirtualDOM wrapper](https://github.com/fazibear/opal-virtual-dom) which served as the starting point for my implementation of Porous. While my final approach may deviate significantly from theirs, having code to review and a workable starting point was invaluable.
data/Rakefile CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- require "rubocop/rake_task"
8
+ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
data/exe/porous CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'porous/cli'
3
5
 
4
6
  Porous::CLI.start ARGV
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porous
4
+ class Application
5
+ include Porous::Component
6
+
7
+ # rubocop:disable Metrics/AbcSize
8
+ def render
9
+ html do
10
+ head do
11
+ meta charset: 'UTF-8'
12
+ meta name: 'viewport', content: 'width=device-width, initial-scale=1.0'
13
+
14
+ if props[:title]
15
+ title do
16
+ text props[:title]
17
+ end
18
+ end
19
+ meta name: 'description', content: props[:description] if props[:description]
20
+
21
+ script src: '/static/dist/application.js'
22
+ script src: '/static/dist/reload.js'
23
+ script src: 'https://cdn.tailwindcss.com'
24
+ end
25
+
26
+ body class: 'bg-gray-50 dark:bg-gray-900' do
27
+ component Porous::Router, props: { path: props[:path], query: props[:query] }
28
+ end
29
+ end
30
+ end
31
+ # rubocop:enable Metrics/AbcSize
32
+ end
33
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  class CLI < Thor
3
5
  include Thor::Actions
@@ -6,17 +8,51 @@ module Porous
6
8
 
7
9
  namespace :build
8
10
 
9
- desc 'build', 'Build static assets'
11
+ def self.exit_on_failure?
12
+ true
13
+ end
10
14
 
15
+ desc 'build', 'Build static assets'
11
16
  def build
12
- empty_directory 'static/dist', force: options[:force]
17
+ empty_directory 'static/dist', verbose: false, force: options[:force]
13
18
  transpile
19
+ live_reload
14
20
  end
15
21
 
22
+ # rubocop:disable Metrics/BlockLength
16
23
  no_commands do
17
24
  def transpile
18
- # TODO: Use Opal::Builder to generate pages, components and entities into static/dist
25
+ components = Dir.glob(File.join('{components,pages}', '**', '*.rb')).map do |relative_path|
26
+ "require '#{relative_path}'"
27
+ end
28
+ build_string = "require 'porous'; #{components.join ";"}".gsub '.rb', ''
29
+ builder = Opal::Builder.new
30
+ builder.build_str build_string, '(inline)'
31
+ File.binwrite "#{Dir.pwd}/static/dist/application.js", builder.to_s
32
+ end
33
+
34
+ # rubocop:disable Metrics/MethodLength
35
+ def live_reload
36
+ timestamp = Time.now.to_i.to_s
37
+ File.write "#{Dir.pwd}/static/dist/timestamp", timestamp
38
+ builder = Opal::Builder.new
39
+ script = <<-BROWSER
40
+ $document.ready do
41
+ every 0.1 do
42
+ Browser::HTTP.get('/static/dist/timestamp').then do |response|
43
+ return unless response.success?
44
+ timestamp = response.text.to_i
45
+ TIMESTAMP ||= timestamp
46
+ $document.location.reload if TIMESTAMP < timestamp
47
+ end
48
+ end
49
+ end
50
+ BROWSER
51
+ builder.build_str script, '(inline)'
52
+ File.binwrite "#{Dir.pwd}/static/dist/reload.js", builder.to_s
19
53
  end
54
+ # rubocop:enable Metrics/MethodLength
20
55
  end
56
+ # rubocop:enable Metrics/BlockLength
21
57
  end
22
58
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  class CLI < Thor
3
5
  include Thor::Actions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  class CLI < Thor
3
5
  check_unknown_options!
@@ -14,13 +16,32 @@ module Porous
14
16
  method_option :host,
15
17
  aliases: :h,
16
18
  type: :string,
17
- default: '127.0.0.1',
19
+ default: 'localhost',
18
20
  desc: 'The host address Porous will bind to'
19
21
 
22
+ MONITORING = %w[components pages].freeze
23
+
20
24
  def server
21
- Rackup::Server.start environment: 'development', builder: <<-BUILDER
22
- run Porous::Server.new
23
- BUILDER
25
+ MONITORING.each { |path| FileUtils.mkdir_p path }
26
+ build
27
+ start_live_reload
28
+ Rackup::Server.start environment: 'none', app: Porous::Server.new
29
+ end
30
+
31
+ no_commands do
32
+ def start_live_reload
33
+ opts = { only: /\.rb$/, relative: true }
34
+ @listener = Listen.to(*MONITORING, opts) do |modified, added, _removed|
35
+ # Load for server
36
+ (modified + added).each do |file|
37
+ load File.expand_path("#{Dir.pwd}/#{file}")
38
+ end
39
+ # Rebuild for browser
40
+ Thread.new { build }
41
+ end
42
+ @listener.start
43
+ at_exit { @listener.stop }
44
+ end
24
45
  end
25
46
  end
26
47
  end
@@ -1,15 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Home
2
4
  include Porous::Page
3
5
  include Porous::Component
4
6
 
5
7
  def route = '/'
8
+ def page_title = 'Porous Web | Home'
9
+ def page_description = 'Landing page generated by Porous'
6
10
 
11
+ # rubocop:disable Metrics, Layout/LineLength
7
12
  def render
8
13
  div class: 'container p-8 mx-auto lg:h-full xl:px-0 flex flex-wrap' do
9
14
  div class: 'flex items-center w-full lg:w-1/2' do
10
15
  div class: 'max-w-2xl mb-8' do
11
16
  h1 class: 'text-4xl font-bold leading-snug tracking-tight text-gray-800 lg:text-4xl lg:leading-tight xl:text-6xl xl:leading-tight dark:text-white' do
12
- span class: "bg-gradient-to-br from-pink-500 to-violet-500 bg-clip-text text-transparent box-decoration-clone" do
17
+ span class: 'bg-gradient-to-br from-pink-500 to-violet-500 bg-clip-text text-transparent box-decoration-clone' do
13
18
  text 'Welcome to Porous!'
14
19
  end
15
20
  end
@@ -19,10 +24,10 @@ class Home
19
24
 
20
25
  div class: 'flex flex-col items-start space-y-3 sm:space-x-4 sm:space-y-0 sm:items-center sm:flex-row' do
21
26
  a href: 'https://github.com/exastencil/porous', target: '_blank', rel: 'noopener',
22
- class: "group relative inline-flex h-12 items-center justify-center overflow-hidden rounded-md bg-indigo-600 px-6 font-medium text-neutral-200 transition hover:scale-110" do
23
- span 'Get Started'
24
- div class: "absolute inset-0 flex h-full w-full justify-center [transform:skew(-12deg)_translateX(-100%)] group-hover:duration-1000 group-hover:[transform:skew(-12deg)_translateX(100%)]" do
25
- div class: "relative h-full w-8 bg-white/20"
27
+ class: 'group relative inline-flex h-12 items-center justify-center overflow-hidden rounded-md bg-indigo-600 px-6 font-medium text-neutral-200 transition hover:scale-110' do
28
+ span 'Get Started 🧽'
29
+ div class: 'absolute inset-0 flex h-full w-full justify-center [transform:skew(-12deg)_translateX(-100%)] group-hover:duration-1000 group-hover:[transform:skew(-12deg)_translateX(100%)]' do
30
+ div class: 'relative h-full w-8 bg-white/20'
26
31
  end
27
32
  end
28
33
  a href: 'https://github.com/exastencil/porous', target: '_blank', rel: 'noopener',
@@ -45,4 +50,5 @@ class Home
45
50
  end
46
51
  end
47
52
  end
53
+ # rubocop:enable Metrics, Layout/LineLength
48
54
  end
data/lib/porous/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
 
3
5
  require 'porous'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Component
3
5
  module ClassMethods
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Component
3
5
  module Render
@@ -5,8 +7,7 @@ module Porous
5
7
  raise Error, "Implement #render in #{self.class} component"
6
8
  end
7
9
 
8
- def before_render
9
- end
10
+ def before_render; end
10
11
 
11
12
  def render_virtual_dom
12
13
  before_render
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Component
3
5
  module Virtual
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Component
3
5
  include VirtualDOM::DOM
@@ -15,7 +17,6 @@ module Porous
15
17
  init_injections
16
18
  inject
17
19
  @virtual_dom = render_virtual_dom
18
- self
19
20
  end
20
21
 
21
22
  def props
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Injection
3
5
  def init; end
@@ -20,16 +22,14 @@ module Porous
20
22
 
21
23
  def init_injections
22
24
  @injections ||= {}
23
- self.class.injections.each do |name, clazz|
24
- if clazz.included_modules.include?(Porous::Injection)
25
- @injections[name] = clazz
26
- .new
27
- .with_root_component(@root_component)
28
- else
29
- raise Error, "Invalid #{clazz} class, should mixin Porous::Injection"
25
+ self.class.injections.each do |name, klass|
26
+ unless klass.included_modules.include?(Porous::Injection)
27
+ raise Error, "Invalid #{klass} class, should mixin Porous::Injection"
30
28
  end
29
+
30
+ @injections[name] = klass.new.with_root_component(@root_component)
31
31
  end
32
- @injections.each do |key, instance|
32
+ @injections.each_value do |instance|
33
33
  instance.inject
34
34
  instance.init
35
35
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porous
4
+ class Logger < Rack::CommonLogger
5
+ def log(env, status, response_headers, began_at)
6
+ super unless env['PATH_INFO'].start_with? 'static'
7
+ end
8
+ end
9
+ end
data/lib/porous/page.rb CHANGED
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  module Page
3
5
  # Define the route according to the Router::Routes rules
4
6
  def route!
5
7
  path = route
6
- @routes ||= Routes.new.tap do |routes|
8
+ @route ||= Routes.new.tap do |routes|
7
9
  routes.route path, to: self.class
8
10
  end
9
11
  end
12
+
13
+ def page_title = 'Porous Web'
14
+ def page_description = nil
10
15
  end
11
16
  end
data/lib/porous/router.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
- module Router
4
+ class Router
3
5
  include Porous::Component
4
6
 
5
7
  attr_reader :params
@@ -23,6 +25,10 @@ module Porous
23
25
  parse_url_params
24
26
  end
25
27
 
28
+ def routes
29
+ @routes.routes
30
+ end
31
+
26
32
  def self.included(base)
27
33
  base.extend(Porous::Component::ClassMethods)
28
34
  end
@@ -34,7 +40,8 @@ module Porous
34
40
 
35
41
  return @route = route
36
42
  end
37
- raise Error, "Can't find route for url"
43
+ available_routes = @routes.routes.map { |r| " #{r[:path]} => #{r[:component]}" }.join
44
+ raise InvalidRouteError, "Unknown route for: #{path}\n\nAvailable routes:\n\n#{available_routes}"
38
45
  end
39
46
 
40
47
  def find_component(route)
@@ -50,9 +57,9 @@ module Porous
50
57
  def call_on_enter_callback(route)
51
58
  return unless route[:on_enter]
52
59
 
53
- if route[:on_enter].respond_to?(:call)
54
- route[:on_enter].call
55
- end
60
+ return unless route[:on_enter].respond_to?(:call)
61
+
62
+ route[:on_enter].call
56
63
  end
57
64
 
58
65
  def go_to(path)
@@ -66,14 +73,16 @@ module Porous
66
73
 
67
74
  def parse_url_params
68
75
  @params = component_url_params
69
- query[1..-1].split('&').each do |param|
76
+ return if query.empty?
77
+
78
+ query[1..].split('&').each do |param|
70
79
  key, value = param.split('=')
71
80
  @params[key] = value
72
- end unless query.empty?
81
+ end
73
82
  end
74
83
 
75
84
  def component_url_params
76
- Hash[@route[:params].zip(path.match(@route[:regex])[1..-1])]
85
+ @route[:params].zip(path.match(@route[:regex])[1..]).to_h
77
86
  end
78
87
 
79
88
  def url_for(name, params = nil)
@@ -91,11 +100,11 @@ module Porous
91
100
  end
92
101
 
93
102
  def query
94
- @props ? @props[:query] : '' # Browser.query
103
+ @props ? @props[:query] : ''
95
104
  end
96
105
 
97
106
  def path
98
- @props ? @props[:path] : '/' # @props[:path] # Browser.path
107
+ @props ? @props[:path] : '/'
99
108
  end
100
109
 
101
110
  def current_url?(name)
@@ -104,9 +113,9 @@ module Porous
104
113
 
105
114
  def url_with_params(route, params)
106
115
  path = route[:path]
107
- params.each do |key, value|
108
- path = path.gsub(":#{key}", "#{value}")
109
- end if params
116
+ params&.each do |key, value|
117
+ path = path.gsub(":#{key}", value.to_s)
118
+ end
110
119
  path
111
120
  end
112
121
  end
data/lib/porous/routes.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Porous
2
4
  class Routes
3
5
  attr_reader :routes
@@ -7,8 +9,9 @@ module Porous
7
9
  @routes = []
8
10
  end
9
11
 
12
+ # rubocop:disable Metrics/AbcSize
10
13
  def route(*params, &block)
11
- path = params.first.gsub(/^\//, '')
14
+ path = params.first.gsub(%r{^/}, '')
12
15
  path = @parent ? "#{@parent}/#{path}" : "/#{path}"
13
16
 
14
17
  add_subroutes(path, &block) if block_given?
@@ -19,12 +22,15 @@ module Porous
19
22
  add_route(params.last[:as], path, params.last[:to], params.last[:props], params.last[:on_enter])
20
23
  end
21
24
  end
25
+ # rubocop:enable Metrics/AbcSize
22
26
 
23
27
  def validate_component(component)
24
28
  raise Error, 'Component not exists' unless component
25
29
 
30
+ return if component.include?(Porous::Component)
31
+
26
32
  raise Error,
27
- "Invalid #{component} class, should mixin Porous::Component" unless component.include?(Porous::Component)
33
+ "Invalid #{component} class, should mixin Porous::Component"
28
34
  end
29
35
 
30
36
  def add_redirect(path, redirect_to)
@@ -51,6 +57,7 @@ module Porous
51
57
  @routes += subroutes.routes
52
58
  end
53
59
 
60
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
54
61
  def build_params_and_regex(path)
55
62
  regex = ['^']
56
63
  params = []
@@ -62,10 +69,10 @@ module Porous
62
69
  regex << '\/'
63
70
  case part[0]
64
71
  when ':'
65
- params << part[1..-1]
72
+ params << part[1..]
66
73
  regex << '([^\/]+)'
67
74
  when '*'
68
- params << part[1..-1]
75
+ params << part[1..]
69
76
  regex << '(.*)'
70
77
  break
71
78
  else
@@ -78,6 +85,7 @@ module Porous
78
85
  params: params
79
86
  }
80
87
  end
88
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
81
89
 
82
90
  def combine(other)
83
91
  @routes += other.routes