livereload 1.2.2 → 1.3
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.
- data/bin/livereload +47 -5
- data/lib/livereload.rb +245 -25
- metadata +18 -5
data/bin/livereload
CHANGED
@@ -1,9 +1,51 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
require 'rubygems'
|
3
3
|
require 'livereload'
|
4
|
+
require 'optparse'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def parse_command_line_config(args)
|
7
|
+
LiveReload::Config.new do |config|
|
8
|
+
|
9
|
+
opts = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: livereload [options] [directory1 directory2 ...]"
|
11
|
+
|
12
|
+
opts.separator ""
|
13
|
+
opts.separator "Browser communication options:"
|
14
|
+
|
15
|
+
opts.on("--address [ADDRESS]", "Network interface to listen on (default is 0.0.0.0)") do |v|
|
16
|
+
config.host = v
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("--port [PORT]", Integer, "TCP port to listen on (default is 10083)") do |v|
|
20
|
+
config.port = v.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.separator ""
|
24
|
+
opts.separator "Debugging options:"
|
25
|
+
|
26
|
+
opts.on("-D", "--[no-]debug", "Print debugging info") do |v|
|
27
|
+
config.debug = v
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.separator ""
|
31
|
+
opts.separator "Common options:"
|
32
|
+
|
33
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
34
|
+
puts opts
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on_tail("--version", "Show version") do
|
39
|
+
puts LiveReload::GEM_VERSION
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.parse!(args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
explicit_config = parse_command_line_config(ARGV)
|
49
|
+
|
50
|
+
directories = if ARGV.size == 0 then ['.'] else ARGV end
|
51
|
+
LiveReload.run directories, explicit_config
|
data/lib/livereload.rb
CHANGED
@@ -1,40 +1,260 @@
|
|
1
1
|
require 'em-websocket'
|
2
2
|
require 'directory_watcher'
|
3
|
+
require 'json/objects'
|
3
4
|
|
4
5
|
# Chrome sometimes sends HTTP/1.0 requests in violation of WebSockets spec
|
6
|
+
# hide the warning about redifinition of a constant
|
7
|
+
saved_stderr = $stderr
|
8
|
+
$stderr = StringIO.new
|
5
9
|
EventMachine::WebSocket::HandlerFactory::PATH = /^(\w+) (\/[^\s]*) HTTP\/1\.[01]$/
|
10
|
+
$stderr = saved_stderr
|
11
|
+
|
12
|
+
class Object
|
13
|
+
def method_missing_with_livereload id, *args, &block
|
14
|
+
if id == :config
|
15
|
+
Object.send(:instance_variable_get, '@livereload_config')
|
16
|
+
else
|
17
|
+
method_missing_without_livereload id, *args, &block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
6
21
|
|
7
22
|
module LiveReload
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
GEM_VERSION = "1.3"
|
24
|
+
API_VERSION = "1.3"
|
25
|
+
|
26
|
+
PROJECT_CONFIG_FILE_TEMPLATE = <<-END.strip.split("\n").collect { |line| line.strip + "\n" }.join("")
|
27
|
+
# Lines starting with pound sign (#) are ignored.
|
28
|
+
|
29
|
+
# additional extensions to monitor
|
30
|
+
#config.exts << 'haml'
|
31
|
+
|
32
|
+
# exclude files with NAMES matching this mask
|
33
|
+
#config.exclusions << '~*'
|
34
|
+
# exclude files with PATHS matching this mask (if the mask contains a slash)
|
35
|
+
#config.exclusions << '/excluded_dir/*'
|
36
|
+
# exclude files with PATHS matching this REGEXP
|
37
|
+
#config.exclusions << /somedir.*(ab){2,4}\.(css|js)$/
|
38
|
+
|
39
|
+
# reload the whole page when .js changes
|
40
|
+
#config.apply_js_live = false
|
41
|
+
# reload the whole page when .css changes
|
42
|
+
#config.apply_css_live = false
|
43
|
+
END
|
44
|
+
|
45
|
+
# note that host and port options do not make sense in per-project config files
|
46
|
+
class Config
|
47
|
+
attr_accessor :host, :port, :exts, :exclusions, :debug, :apply_js_live, :apply_css_live
|
48
|
+
|
49
|
+
def initialize &block
|
50
|
+
@host = nil
|
51
|
+
@port = nil
|
52
|
+
@debug = nil
|
53
|
+
@exts = []
|
54
|
+
@exclusions = []
|
55
|
+
@apply_js_live = nil
|
56
|
+
@apply_css_live = nil
|
57
|
+
|
58
|
+
update!(&block) if block
|
59
|
+
end
|
60
|
+
|
61
|
+
def update!
|
62
|
+
yield self
|
63
|
+
|
64
|
+
# remove leading dots
|
65
|
+
@exts = @exts.collect { |e| e.sub(/^\./, '') }
|
66
|
+
end
|
67
|
+
|
68
|
+
def merge! other
|
69
|
+
@host = other.host if other.host
|
70
|
+
@port = other.port if other.port
|
71
|
+
@exts += other.exts
|
72
|
+
@exclusions = other.exclusions + @exclusions
|
73
|
+
@debug = other.debug if other.debug != nil
|
74
|
+
@apply_js_live = other.apply_js_live if other.apply_js_live != nil
|
75
|
+
@apply_css_live = other.apply_css_live if other.apply_css_live != nil
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
def load_from file
|
82
|
+
Config.new do |config|
|
83
|
+
if File.file? file
|
84
|
+
Object.send(:instance_variable_set, '@livereload_config', config)
|
85
|
+
Object.send(:alias_method, :method_missing_without_livereload, :method_missing)
|
86
|
+
Object.send(:alias_method, :method_missing, :method_missing_with_livereload)
|
87
|
+
load file, true
|
88
|
+
Object.send(:alias_method, :method_missing, :method_missing_without_livereload)
|
89
|
+
Object.send(:instance_variable_set, '@livereload_config', nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def merge *configs
|
95
|
+
configs.reduce(Config.new) { |merged, config| config && merged.merge!(config) || merged }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
DEFAULT_CONFIG = Config.new do |config|
|
101
|
+
config.debug = false
|
102
|
+
config.host = '0.0.0.0'
|
103
|
+
config.port = 10083
|
104
|
+
config.exts = %w/html css js png gif jpg php php5 py rb erb/
|
105
|
+
config.exclusions = %w!*/.git/* */.svn/* */.hg/*!
|
106
|
+
config.apply_js_live = true
|
107
|
+
config.apply_css_live = true
|
108
|
+
end
|
109
|
+
|
110
|
+
USER_CONFIG_FILE = File.expand_path("~/.livereload")
|
111
|
+
USER_CONFIG = Config.load_from(USER_CONFIG_FILE)
|
112
|
+
|
113
|
+
class Project
|
114
|
+
attr_reader :config
|
115
|
+
|
116
|
+
def initialize directory, explicit_config=nil
|
117
|
+
@directory = directory
|
118
|
+
@explicit_config = explicit_config
|
119
|
+
read_config
|
120
|
+
end
|
121
|
+
|
122
|
+
def read_config
|
123
|
+
project_config_file = File.join(@directory, '.livereload')
|
124
|
+
unless File.file? project_config_file
|
125
|
+
File.open(project_config_file, 'w') do |file|
|
126
|
+
file.write PROJECT_CONFIG_FILE_TEMPLATE
|
127
|
+
end
|
128
|
+
end
|
129
|
+
project_config = Config.load_from project_config_file
|
130
|
+
@config = Config.merge(DEFAULT_CONFIG, USER_CONFIG, project_config, @explicit_config)
|
131
|
+
end
|
132
|
+
|
133
|
+
def print_config
|
134
|
+
puts "Watching: #{@directory}"
|
135
|
+
puts " - extensions: " + @config.exts.collect {|e| ".#{e}"}.join(" ")
|
136
|
+
if !@config.apply_js_live && !@config.apply_css_live
|
137
|
+
puts " - live refreshing disabled for .css & .js: will reload the whole page on every change"
|
138
|
+
elsif !@config.apply_js_live
|
139
|
+
puts " - live refreshing disabled for .js: will reload the whole page when .js is changed"
|
140
|
+
elsif !@config.apply_css_live
|
141
|
+
puts " - live refreshing disabled for .css: will reload the whole page when .css is changed"
|
142
|
+
end
|
143
|
+
if @config.exclusions.size > 0
|
144
|
+
puts " - excluding changes in: " + @config.exclusions.join(" ")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_excluded? path
|
149
|
+
basename = File.basename(path)
|
150
|
+
@config.exclusions.any? do |exclusion|
|
151
|
+
if Regexp === exclusion
|
152
|
+
path =~ exclusion
|
153
|
+
elsif exclusion.include? '/'
|
154
|
+
File.fnmatch?(File.join(@directory, exclusion), path)
|
155
|
+
else
|
156
|
+
File.fnmatch?(exclusion, basename)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def when_changes_detected &block
|
162
|
+
@when_changes_detected = block
|
163
|
+
end
|
164
|
+
|
165
|
+
def restart_watching
|
166
|
+
if @dw
|
167
|
+
@dw.stop
|
168
|
+
end
|
169
|
+
@dw = DirectoryWatcher.new @directory, :glob => "{.livereload,**/*.{#{@config.exts.join(',')}}}", :scanner => :em, :pre_load => true
|
170
|
+
@dw.add_observer do |*args|
|
171
|
+
begin
|
172
|
+
args.each do |event|
|
173
|
+
path = event[:path]
|
174
|
+
if File.basename(path) == '.livereload'
|
175
|
+
@when_changes_detected.call [:config_changed, path]
|
176
|
+
elsif event[:type] == :modified
|
177
|
+
@when_changes_detected.call [if is_excluded?(path) then :excluded else :modified end, path]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
rescue
|
181
|
+
puts $!
|
182
|
+
puts $!.backtrace
|
183
|
+
end
|
184
|
+
end
|
185
|
+
@dw.start
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.configure
|
190
|
+
yield Config
|
21
191
|
end
|
22
192
|
|
23
|
-
def self.run(
|
193
|
+
def self.run(directories, explicit_config)
|
194
|
+
# EventMachine needs to run kqueue for the watching API to work
|
24
195
|
EM.kqueue = true if EM.kqueue?
|
25
|
-
|
26
|
-
|
196
|
+
|
197
|
+
web_sockets = []
|
198
|
+
|
199
|
+
# for host and port
|
200
|
+
global_config = Config.merge(DEFAULT_CONFIG, USER_CONFIG, explicit_config)
|
201
|
+
directories = directories.collect { |directory| File.expand_path(directory) }
|
202
|
+
projects = directories.collect { |directory| Project.new(directory, explicit_config) }
|
27
203
|
|
28
204
|
puts
|
29
|
-
puts "Version:
|
30
|
-
puts "Port:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
205
|
+
puts "Version: #{GEM_VERSION} (compatible with browser extension versions #{API_VERSION}.x)"
|
206
|
+
puts "Port: #{global_config.port}"
|
207
|
+
projects.each { |project| project.print_config }
|
208
|
+
|
209
|
+
EventMachine.run do
|
210
|
+
projects.each do |project|
|
211
|
+
project.when_changes_detected do |event, modified_file|
|
212
|
+
case event
|
213
|
+
when :config_changed
|
214
|
+
puts
|
215
|
+
puts ">> Configuration change: " + modified_file
|
216
|
+
puts
|
217
|
+
EventMachine.next_tick do
|
218
|
+
projects.each { |project| project.read_config; project.print_config }
|
219
|
+
puts
|
220
|
+
projects.each { |project| project.restart_watching }
|
221
|
+
end
|
222
|
+
when :excluded
|
223
|
+
puts "Excluded: #{File.basename(modified_file)}"
|
224
|
+
when :modified
|
225
|
+
puts "Modified: #{File.basename(modified_file)}"
|
226
|
+
data = ['refresh', { :path => modified_file,
|
227
|
+
:apply_js_live => project.config.apply_js_live,
|
228
|
+
:apply_css_live => project.config.apply_css_live }].to_json
|
229
|
+
puts data if global_config.debug
|
230
|
+
web_sockets.each do |ws|
|
231
|
+
ws.send data
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
projects.each { |project| project.restart_watching }
|
238
|
+
|
239
|
+
puts
|
240
|
+
puts "LiveReload is waiting for browser to connect."
|
241
|
+
EventMachine::WebSocket.start(:host => global_config.host, :port => global_config.port, :debug => global_config.debug) do |ws|
|
242
|
+
ws.onopen do
|
243
|
+
begin
|
244
|
+
puts "Browser connected."; ws.send "!!ver:#{API_VERSION}"; web_sockets << ws
|
245
|
+
rescue
|
246
|
+
puts $!
|
247
|
+
puts $!.backtrace
|
248
|
+
end
|
249
|
+
end
|
250
|
+
ws.onmessage do |msg|
|
251
|
+
puts "Browser URL: #{msg}"
|
252
|
+
end
|
253
|
+
ws.onclose do
|
254
|
+
web_sockets.delete ws
|
255
|
+
puts "Browser disconnected."
|
256
|
+
end
|
257
|
+
end
|
38
258
|
end
|
39
259
|
end
|
40
260
|
end
|
metadata
CHANGED
@@ -4,9 +4,8 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
-
-
|
8
|
-
|
9
|
-
version: 1.2.2
|
7
|
+
- 3
|
8
|
+
version: "1.3"
|
10
9
|
platform: ruby
|
11
10
|
authors:
|
12
11
|
- Andrey Tarantsov
|
@@ -45,7 +44,21 @@ dependencies:
|
|
45
44
|
version: 1.3.2
|
46
45
|
type: :runtime
|
47
46
|
version_requirements: *id002
|
48
|
-
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ruby-json
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 1
|
56
|
+
- 1
|
57
|
+
- 2
|
58
|
+
version: 1.1.2
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id003
|
61
|
+
description: " LiveReload is a Safari/Chrome extension + a command-line tool that:\n 1. Applies CSS and JavaScript file changes without reloading a page.\n 2. Automatically reloads a page when any other file changes (html, image, server-side script, etc).\n"
|
49
62
|
email: andreyvit@gmail.com
|
50
63
|
executables:
|
51
64
|
- livereload
|
@@ -85,6 +98,6 @@ rubyforge_project:
|
|
85
98
|
rubygems_version: 1.3.6
|
86
99
|
signing_key:
|
87
100
|
specification_version: 3
|
88
|
-
summary: A command-line server for the LiveReload Safari extension
|
101
|
+
summary: A command-line server for the LiveReload Safari/Chrome extension
|
89
102
|
test_files: []
|
90
103
|
|