ruby-live-reload 0.2.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d9145bba59fc4b7d871e48c6c2c5b6e8d2d66f991a267540c9f74867ad3c5728
4
+ data.tar.gz: 87908f936203b08cf1e03408da428bc73f01bc5a880612d525eebcf169dfc835
5
+ SHA512:
6
+ metadata.gz: 53d12a050bcf33fa115496e1176a39f1d4bac46b1426d9dd7184b7da7e2226ebf9f337b2402452d006c9ee1f8f27592e36a5d9a7d806489b81d971693121a48a
7
+ data.tar.gz: 41c6fc10519d6d69792619fba889edb6edec12a2b9ef1d1a8fe3a8b77372ce7e81feee6f7eac1db8bdc1c516494a401995ef00236170a5497cbf62f71c066825
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 James
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ✨ Ruby Live Reload ✨
2
+
3
+ A Ruby live server for web development
data/bin/rlr ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Alias for bin/ruby-live-reload
4
+ load File.expand_path("../ruby-live-reload", __FILE__)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ruby_live_reload.rb"
@@ -0,0 +1,70 @@
1
+ module RubyLiveReload
2
+ class Options
3
+
4
+ private
5
+
6
+ attr_writer :host, :port, :directory, :proxy, :message
7
+
8
+ def initialize
9
+ @host = "127.0.0.1"
10
+ @port = 8080
11
+ @directory = Dir.pwd
12
+ @proxy = nil
13
+ @message = nil
14
+ end
15
+
16
+ public
17
+
18
+ attr_reader :host, :port, :directory, :proxy, :message
19
+
20
+ def self.parse(args)
21
+
22
+ unless [String, Array].include? args.class
23
+ raise <<~ERROR.strip
24
+ Provide an array or a space-separated string of arguments: ["-p", 9090, "--host", "198.168.0.42"] or "-p 9090 --host 192.168.0.42"
25
+ ERROR
26
+ end
27
+
28
+ args = args.split " " if args.is_a? String
29
+
30
+ instance = Options.new
31
+
32
+ OptionParser.new do |options|
33
+ options.banner = "Usage: rlr [instance]"
34
+ options.release = VERSION
35
+
36
+ options.on("-b HOST", "--bind HOST", "Hostname") do |host|
37
+ instance.send :host=, host
38
+ end
39
+
40
+ options.on("-p PORT", "--port PORT", "Port") do |port|
41
+ instance.send :port=, port
42
+ end
43
+
44
+ options.on("--proxy URL", "Url of the proxied app") do |proxy|
45
+ instance.send :proxy=, proxy
46
+ end
47
+
48
+ options.on("-d PATH", "--directory PATH", "Path") do |path|
49
+ directory = if path.start_with?("/")
50
+ path
51
+ else
52
+ File.join(Dir.pwd, path)
53
+ end
54
+
55
+ # TODO Validate path is a directory
56
+
57
+ instance.send :directory=, directory
58
+ end
59
+
60
+ options.on("-v", "--version", "Version") do
61
+ instance.send :message=, VERSION
62
+ end
63
+
64
+ end.parse(args)
65
+
66
+ return instance
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,3 @@
1
+ module RubyLiveReload
2
+ VERSION = "0.2.7"
3
+ end
@@ -0,0 +1,212 @@
1
+ require "optparse"
2
+ require "sinatra/base"
3
+ require "filewatcher"
4
+ require "faraday"
5
+
6
+ require_relative "ruby_live_reload/version.rb"
7
+ require_relative "ruby_live_reload/options.rb"
8
+
9
+ # https://blog.appsignal.com/2024/11/27/server-sent-events-and-websockets-in-rack-for-ruby.html
10
+
11
+ module RubyLiveReload
12
+
13
+ class Server < Sinatra::Base
14
+
15
+ set :quiet, true
16
+ set :server_settings, { max_threads: 256, Silent: true }
17
+
18
+ set :clients, Set.new
19
+
20
+ on_start do
21
+ puts <<~_
22
+ Ruby Live Reload running with #{settings.threads} threads!
23
+ Watching #{settings.directory}
24
+
25
+ http://#{settings.bind}:#{settings.port}
26
+
27
+ Ctrl-c to stop
28
+ _
29
+
30
+
31
+ @filewatcher_thread = Thread.new do
32
+ @filewatcher = Filewatcher.new File.join(settings.directory, "**", "*.*")
33
+
34
+ @filewatcher.watch do |changes|
35
+ settings.clients.each do |client|
36
+ client << "data: " + changes.to_s + "\n\n"
37
+ rescue
38
+ client.close
39
+ settings.clients.delete client
40
+ end
41
+ end
42
+
43
+ settings.clients.each do |client|
44
+ client.close
45
+ end
46
+
47
+ rescue
48
+ exit # Watcher thread crash
49
+ end
50
+ end
51
+
52
+ on_stop do
53
+ @filewatcher&.stop()
54
+ @filewatcher_thread&.join
55
+ end
56
+
57
+ get "/ruby-live-reload-sse", provides: "text/event-stream" do
58
+ stream :keep_open do |client|
59
+ if settings.clients.add? client
60
+ client.callback do # on connection closed
61
+ settings.clients.delete client
62
+ end
63
+ end
64
+
65
+ # Throttle Sinatra scheduler since Filewatcher is used to trigger SSE
66
+ sleep 1
67
+
68
+ # Heartbeat will detect disconnected client and free the thread
69
+ client << "event: heartbeat\n\n"
70
+ rescue
71
+ settings.clients.delete client
72
+ client.close
73
+ end
74
+ end
75
+
76
+ # Tweak to remove 404 if favicon is missing
77
+ get "/favicon.ico" do
78
+ path = File.join(settings.directory, "favicon.ico")
79
+
80
+ if File.exist? path
81
+ send_file path
82
+ end
83
+
84
+ 204
85
+ end
86
+
87
+ get "*" do
88
+ headers \
89
+ "Cache-Control" => "max-age=0, no-cache, no-store, must-revalidate",
90
+ "Expires"=> "Thu, 01 Jan 1970 00:00:00 GMT",
91
+ "Pragma" => "no-cache"
92
+
93
+ splat = File.join params["splat"]
94
+ path = File.join(settings.directory, splat)
95
+ is_asset = !([".html", ".htm", ".xhtml"].include? File.extname(path))
96
+
97
+ if File.directory?(path) && !path.end_with?("/")
98
+ # Add / to the end of URL so browser correctly handles relative paths
99
+ redirect "#{splat}/"
100
+ elsif File.file?(path) && is_asset
101
+ send_file path
102
+ end
103
+
104
+ response = if settings.proxy
105
+ # TODO Handle response other than HTML
106
+ # --wrap to enclose arbitrary text within HTML to allow snippet injection?
107
+ # What about arbitrary files? Images, CSS, etc?
108
+ Faraday.get(File.join(settings.proxy + splat)).body
109
+ elsif File.file? path
110
+ File.read path
111
+ elsif File.file? File.join(path, "index.html")
112
+ File.read File.join(path, "index.html")
113
+ elsif File.directory?(path) && !response
114
+ children = Dir.glob("*", base: path).sort_by { |s| [File.directory?(s).to_s, s.downcase] }
115
+ links = children.map do |child|
116
+ <<-LI
117
+ <li>
118
+ <a href="#{child}">
119
+ #{child + (File.directory?(child) ? "/" : "")}
120
+ </a>
121
+ </li>
122
+ LI
123
+ end.join
124
+
125
+ <<-LISTING
126
+ <!DOCTYPE html>
127
+ <html lang="en">
128
+ <head>
129
+ <meta charset="UTF-8">
130
+ <title>RLR Directory Listing</title>
131
+ </head>
132
+ <body>
133
+ <h1>Ruby Live Reload</h1>
134
+
135
+ <p>#{path}</p>
136
+ <ul>
137
+ #{links}
138
+ </ul>
139
+ </body>
140
+ </html>
141
+ LISTING
142
+ elsif !response
143
+ status 404
144
+
145
+ <<-NOT_FOUND
146
+ <!DOCTYPE html>
147
+ <html lang="en">
148
+ <head>
149
+ <meta charset="UTF-8">
150
+ <title></title>
151
+ </head>
152
+ <body>
153
+ <h1>File Not Found</h1>
154
+
155
+ <p>#{path}</p>
156
+ </body>
157
+ </html>
158
+ NOT_FOUND
159
+ end
160
+
161
+ client_js = <<-JS
162
+ <script defer>
163
+ const source = new EventSource('/ruby-live-reload-sse')
164
+
165
+ source.onmessage = (m) => {
166
+ location.reload()
167
+ }
168
+
169
+ source.onerror = (m) => {
170
+ console.group('Ruby Live Reload')
171
+ console.warn('Cannot reach server, reconnecting...')
172
+ console.groupEnd()
173
+ }
174
+
175
+ addEventListener("beforeunload", (event) => {
176
+ source.close()
177
+
178
+ return false // Do not show confirm dialog
179
+ })
180
+ </script>
181
+ JS
182
+
183
+ head_inject = response.sub!(/<\/head>/, "#{client_js}</head>")
184
+ body_inject = unless head_inject
185
+ response.sub!(/<body(.*)>/, "<body#{$+}>#{client_js}")
186
+ end
187
+
188
+ if head_inject || body_inject
189
+ content_type :html
190
+ end
191
+
192
+ response
193
+ end
194
+
195
+ end
196
+
197
+
198
+ options = Options.parse(ARGV)
199
+
200
+ if message = options.message
201
+ puts message
202
+ else
203
+ Server.run!({
204
+ bind: options.host,
205
+ port: options.port,
206
+ directory: options.directory,
207
+ proxy: options.proxy,
208
+ threads: 16,
209
+ })
210
+ end
211
+
212
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-live-reload
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.7
5
+ platform: ruby
6
+ authors:
7
+ - James Hoffman
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 13.2.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 13.2.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest-reporters
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.7.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.7.1
40
+ - !ruby/object:Gem::Dependency
41
+ name: sinatra
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 4.1.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 4.1.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: puma
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 6.5.0
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 6.5.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: rackup
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 2.2.0
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 2.2.0
82
+ - !ruby/object:Gem::Dependency
83
+ name: filewatcher
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 2.0.0
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 2.0.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: faraday
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 2.12.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 2.12.0
110
+ description: Serve a web app and enable page refresh when files change
111
+ email: james@jhoffman.ca
112
+ executables:
113
+ - rlr
114
+ - ruby-live-reload
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE
119
+ - README.md
120
+ - bin/rlr
121
+ - bin/ruby-live-reload
122
+ - lib/ruby_live_reload.rb
123
+ - lib/ruby_live_reload/options.rb
124
+ - lib/ruby_live_reload/version.rb
125
+ homepage: https://github.com/thecodingjames/ruby-live-reload
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.6.9
144
+ specification_version: 4
145
+ summary: A Ruby live server for web development
146
+ test_files: []