livereload 1.2.2 → 1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|