glim 0.1.1
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 +7 -0
- data/bin/glim +1538 -0
- data/lib/cache.rb +66 -0
- data/lib/commands.rb +261 -0
- data/lib/exception.rb +18 -0
- data/lib/liquid_ext.rb +249 -0
- data/lib/local_server.rb +375 -0
- data/lib/log_and_profile.rb +115 -0
- data/lib/version.rb +3 -0
- metadata +178 -0
data/lib/local_server.rb
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
require 'exception'
|
2
|
+
require 'listen'
|
3
|
+
require 'mime/types'
|
4
|
+
require 'socket'
|
5
|
+
require 'webrick'
|
6
|
+
require 'websocket'
|
7
|
+
|
8
|
+
module WebSocket
|
9
|
+
class Connection
|
10
|
+
attr_reader :socket
|
11
|
+
|
12
|
+
def self.establish(socket)
|
13
|
+
handshake = WebSocket::Handshake::Server.new
|
14
|
+
handshake << socket.gets until handshake.finished?
|
15
|
+
|
16
|
+
raise "Malformed handshake received from WebSocket client" unless handshake.valid?
|
17
|
+
|
18
|
+
socket.puts(handshake.to_s)
|
19
|
+
Connection.new(socket, handshake)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(socket, handshake)
|
23
|
+
@socket = socket
|
24
|
+
@handshake = handshake
|
25
|
+
end
|
26
|
+
|
27
|
+
def puts(message)
|
28
|
+
frame = WebSocket::Frame::Outgoing::Server.new(version: @handshake.version, data: message, type: :text)
|
29
|
+
@socket.puts(frame.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_message
|
33
|
+
frame = WebSocket::Frame::Incoming::Server.new(version: @handshake.version)
|
34
|
+
frame << @socket.read_nonblock(4096)
|
35
|
+
while message = frame.next
|
36
|
+
yield message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Server
|
42
|
+
def initialize(host: 'localhost', port: nil)
|
43
|
+
@server, @rd_pipe, @wr_pipe = TCPServer.new(host, port), *IO.pipe
|
44
|
+
end
|
45
|
+
|
46
|
+
def broadcast(message)
|
47
|
+
@wr_pipe.puts(message)
|
48
|
+
end
|
49
|
+
|
50
|
+
def shutdown
|
51
|
+
broadcast('shutdown')
|
52
|
+
|
53
|
+
@wr_pipe.close
|
54
|
+
@wr_pipe = nil
|
55
|
+
|
56
|
+
@thread.join
|
57
|
+
|
58
|
+
@server.close
|
59
|
+
@server = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def start
|
63
|
+
@thread = Thread.new do
|
64
|
+
connections = []
|
65
|
+
running = true
|
66
|
+
while running
|
67
|
+
rs, _, _ = IO.select([ @server, @rd_pipe, *connections.map { |conn| conn.socket } ])
|
68
|
+
rs.each do |socket|
|
69
|
+
if socket == @server
|
70
|
+
socket = @server.accept
|
71
|
+
begin
|
72
|
+
connections << Connection.establish(socket)
|
73
|
+
rescue => e
|
74
|
+
$log.warn("Failed to perform handshake with new WebSocket client: #{e}", e)
|
75
|
+
socket.close
|
76
|
+
end
|
77
|
+
elsif socket == @rd_pipe
|
78
|
+
message = @rd_pipe.gets.chomp
|
79
|
+
if message == 'shutdown'
|
80
|
+
running = false
|
81
|
+
break
|
82
|
+
end
|
83
|
+
$log.debug("Send ‘#{message}’ to #{connections.count} WebSocket #{connections.count == 1 ? 'client' : 'clients'}") unless connections.empty?
|
84
|
+
connections.each do |conn|
|
85
|
+
begin
|
86
|
+
conn.puts(message)
|
87
|
+
rescue => e
|
88
|
+
$log.warn("Error writing to WebSocket client socket: #{e}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
else
|
92
|
+
if conn = connections.find { |candidate| candidate.socket == socket }
|
93
|
+
begin
|
94
|
+
conn.each_message do |frame|
|
95
|
+
$log.debug("Received #{frame.to_s.size} bytes from WebSocket client: #{frame}") unless frame.to_s.empty?
|
96
|
+
end
|
97
|
+
rescue IO::WaitReadable
|
98
|
+
$log.warn("IO::WaitReadable exception while reading from WebSocket client")
|
99
|
+
rescue EOFError
|
100
|
+
conn.socket.close
|
101
|
+
connections.delete(conn)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
@rd_pipe.close
|
108
|
+
@rd_pipe = nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
module Glim
|
115
|
+
module LocalServer
|
116
|
+
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
117
|
+
@@mutex = Mutex.new
|
118
|
+
|
119
|
+
def initialize(server, config)
|
120
|
+
@config = config
|
121
|
+
end
|
122
|
+
|
123
|
+
def do_GET(request, response)
|
124
|
+
@@mutex.synchronize do
|
125
|
+
do_GET_impl(request, response)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def do_GET_impl(request, response)
|
130
|
+
status, mime_type, body, file = 200, nil, nil, nil
|
131
|
+
|
132
|
+
if request.path == '/.ws/script.js'
|
133
|
+
mime_type, body = self.mime_type_for(request.path), self.websocket_script
|
134
|
+
elsif page = self.find_page(request.path)
|
135
|
+
file = page
|
136
|
+
elsif dir = self.find_directory(request.path)
|
137
|
+
if request.path.end_with?('/')
|
138
|
+
if request.path == '/' || @config['show_dir_listing']
|
139
|
+
mime_type, body = 'text/html', self.directory_index_for_path(dir)
|
140
|
+
else
|
141
|
+
$log.warn("Directory index forbidden for: #{request.path}")
|
142
|
+
status = 403
|
143
|
+
end
|
144
|
+
else
|
145
|
+
response['Location'] = "#{dir}/"
|
146
|
+
status = 302
|
147
|
+
end
|
148
|
+
else
|
149
|
+
$log.warn("No file for request: #{request.path}")
|
150
|
+
status = 404
|
151
|
+
end
|
152
|
+
|
153
|
+
if status != 200 && body.nil? && file.nil?
|
154
|
+
unless file = self.find_error_page(status, request.path)
|
155
|
+
mime_type, body = 'text/html', self.error_page_for_status(status, request.path)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
mime_type ||= file ? self.mime_type_for(file.output_path('/')) : 'text/plain'
|
160
|
+
body ||= content_for_file(file)
|
161
|
+
|
162
|
+
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
163
|
+
response['Pragma'] = 'no-cache'
|
164
|
+
response['Expires'] = '0'
|
165
|
+
response.status = status
|
166
|
+
response.content_type = mime_type
|
167
|
+
response.body = mime_type.start_with?('text/html') ? inject_reload_script(body) : body
|
168
|
+
end
|
169
|
+
|
170
|
+
def content_for_file(file)
|
171
|
+
if file.frontmatter?
|
172
|
+
begin
|
173
|
+
file.output
|
174
|
+
rescue Glim::Error => e
|
175
|
+
content = "<pre>#{e.messages.join("\n")}</pre>"
|
176
|
+
self.create_page("Error", "Exception raised for <code>#{file}</code>", content)
|
177
|
+
rescue => e
|
178
|
+
content = "<pre>#{e.to_s}</pre>"
|
179
|
+
self.create_page("Error", "Exception raised for <code>#{file}</code>", content)
|
180
|
+
end
|
181
|
+
else
|
182
|
+
File.read(file.path)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def find_page(path)
|
187
|
+
self.files.find do |file|
|
188
|
+
candidate = file.output_path('/')
|
189
|
+
if path == candidate || path + File.extname(candidate) == candidate
|
190
|
+
true
|
191
|
+
elsif path.end_with?('/')
|
192
|
+
File.basename(candidate, '.*') == 'index' && path + File.basename(candidate) == candidate
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def find_error_page(status, path)
|
198
|
+
candidates = self.files.select do |file|
|
199
|
+
file.basename == status.to_s && path_descends_from?(path, File.dirname(file.output_path('/')))
|
200
|
+
end
|
201
|
+
candidates.max { |lhs, rhs| lhs.output_path('/').size <=> rhs.output_path('/').size }
|
202
|
+
end
|
203
|
+
|
204
|
+
def find_directory(path)
|
205
|
+
path = path.chomp('/') unless path == '/'
|
206
|
+
self.files.map { |file| File.dirname(file.output_path('/')) }.find { |dir| path == dir }
|
207
|
+
end
|
208
|
+
|
209
|
+
def directory_index_for_path(path)
|
210
|
+
candidates = self.files.map { |file| file.output_path('/') }
|
211
|
+
candidates = candidates.select { |candidate| path_descends_from?(candidate, path) }
|
212
|
+
candidates = candidates.map { |candidate| candidate.sub(/(^#{Regexp.escape(path.chomp('/'))}\/[^\/]+\/?).*/, '\1') }.sort.uniq
|
213
|
+
candidates.unshift(path + '/..') if path != '/'
|
214
|
+
|
215
|
+
heading = "Index of <code>#{path}</code>"
|
216
|
+
content = candidates.map do |candidate|
|
217
|
+
"<li><a href = '#{candidate}'>#{candidate.sub(/.*?([^\/]+\/?)$/, '\1')}</a></li>"
|
218
|
+
end
|
219
|
+
|
220
|
+
self.create_page("Directory Index", heading, "<ul>#{content.join("\n")}</ul>")
|
221
|
+
end
|
222
|
+
|
223
|
+
def error_page_for_status(status, path)
|
224
|
+
case status
|
225
|
+
when 302 then title, heading, content = "302 Redirecting…", "Redirecting…", "Your browser should have redirected you."
|
226
|
+
when 403 then title, heading, content = "403 Forbidden", "Forbidden", "You don't have permission to access <code>#{path}</code> on this server."
|
227
|
+
when 404 then title, heading, content = "404 Not Found", "Not Found", "The requested URL <code>#{path}</code> was not found on this server."
|
228
|
+
else title, heading, content = "Error #{status}", "Error #{status}", "No detailed description of this error."
|
229
|
+
end
|
230
|
+
self.create_page(title, heading, content)
|
231
|
+
end
|
232
|
+
|
233
|
+
def websocket_script
|
234
|
+
<<~JS
|
235
|
+
const glim = {
|
236
|
+
connect: function (host, port, should_try, should_reload) {
|
237
|
+
const server = host + ":" + port
|
238
|
+
console.log("Connecting to Glim’s live reload server (" + server + ")…");
|
239
|
+
|
240
|
+
const socket = new WebSocket("ws://" + server + "/socket");
|
241
|
+
|
242
|
+
socket.onopen = () => {
|
243
|
+
console.log("Established connection: Live reload enabled.")
|
244
|
+
if(should_reload) {
|
245
|
+
document.location.reload(true);
|
246
|
+
}
|
247
|
+
};
|
248
|
+
|
249
|
+
socket.onmessage = (event) => {
|
250
|
+
console.log("Message from live reload server: " + event.data);
|
251
|
+
|
252
|
+
if(event.data == 'reload') {
|
253
|
+
document.location.reload(true);
|
254
|
+
}
|
255
|
+
else if(event.data == 'close') {
|
256
|
+
window.close();
|
257
|
+
}
|
258
|
+
};
|
259
|
+
|
260
|
+
socket.onclose = () => {
|
261
|
+
console.log("Lost connection: Live reload disabled.")
|
262
|
+
|
263
|
+
if(should_try) {
|
264
|
+
window.setTimeout(() => this.connect(host, port, should_try, true), 2500);
|
265
|
+
}
|
266
|
+
};
|
267
|
+
},
|
268
|
+
};
|
269
|
+
|
270
|
+
glim.connect('#{@config['host']}', #{@config['livereload_port']}, true /* should_try */, false /* should_reload */);
|
271
|
+
JS
|
272
|
+
end
|
273
|
+
|
274
|
+
def path_descends_from?(path, parent)
|
275
|
+
parent == '/' || path[parent.chomp('/').size] == '/' && path.start_with?(parent)
|
276
|
+
end
|
277
|
+
|
278
|
+
def create_page(title, heading, content)
|
279
|
+
<<~HTML
|
280
|
+
<style>body {
|
281
|
+
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
282
|
+
}
|
283
|
+
</style>
|
284
|
+
<title>#{title}</title>
|
285
|
+
<h1>#{heading}</h1>
|
286
|
+
#{content}
|
287
|
+
HTML
|
288
|
+
end
|
289
|
+
|
290
|
+
def inject_reload_script(content)
|
291
|
+
return content unless @config['livereload']
|
292
|
+
|
293
|
+
script_tag = "<script src='#{@config['url']}/.ws/script.js'></script>"
|
294
|
+
if content =~ /<head.*?>/
|
295
|
+
content = "#$`#$&#{script_tag}#$'"
|
296
|
+
elsif content =~ /<html.*?>/
|
297
|
+
content = "#$`#$&#{script_tag}#$'"
|
298
|
+
else
|
299
|
+
content = script_tag + content
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def files
|
304
|
+
@config.site.files_and_documents.select { |file| file.write? }
|
305
|
+
end
|
306
|
+
|
307
|
+
def mime_type_for(filename, encoding = nil)
|
308
|
+
if type = MIME::Types.type_for(filename).shift
|
309
|
+
if type.ascii? || type.media_type == 'text' || %w( ecmascript javascript ).include?(type.sub_type)
|
310
|
+
"#{type.content_type}; charset=#{encoding || @config['encoding']}"
|
311
|
+
else
|
312
|
+
type.content_type
|
313
|
+
end
|
314
|
+
else
|
315
|
+
'application/octet-stream'
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.start(config)
|
321
|
+
config['url'] = "http://#{config['host']}:#{config['port']}"
|
322
|
+
project_dir = config.site.project_dir
|
323
|
+
|
324
|
+
websocket_server, listener = nil, nil
|
325
|
+
|
326
|
+
if config['livereload']
|
327
|
+
websocket_server = WebSocket::Server.new(host: config['host'], port: config['livereload_port'])
|
328
|
+
websocket_server.start
|
329
|
+
end
|
330
|
+
|
331
|
+
server = WEBrick::HTTPServer.new(
|
332
|
+
BindAddress: config['host'],
|
333
|
+
Port: config['port'],
|
334
|
+
Logger: WEBrick::Log.new('/dev/null'),
|
335
|
+
AccessLog: [],
|
336
|
+
)
|
337
|
+
|
338
|
+
server.mount('/', Servlet, config)
|
339
|
+
|
340
|
+
if config['watch'] || config['livereload']
|
341
|
+
listener = Listen.to(project_dir) do |modified, added, removed|
|
342
|
+
paths = [ *modified, *added, *removed ]
|
343
|
+
$log.debug("File changes detected for: #{paths.select { |path| path.start_with?(project_dir) }.map { |path| Util.relative_path(path, project_dir) }.join(', ')}")
|
344
|
+
config.reload
|
345
|
+
websocket_server.broadcast('reload') if websocket_server
|
346
|
+
end
|
347
|
+
$log.debug("Watching #{project_dir} for changes")
|
348
|
+
listener.start
|
349
|
+
end
|
350
|
+
|
351
|
+
trap("INT") do
|
352
|
+
server.shutdown
|
353
|
+
end
|
354
|
+
|
355
|
+
if config['open_url'] && File.executable?('/usr/bin/open')
|
356
|
+
page = config.site.links['.']
|
357
|
+
system('/usr/bin/open', page ? page.url : config['url'])
|
358
|
+
end
|
359
|
+
|
360
|
+
$log.info("Starting server on #{config['url']}")
|
361
|
+
server.start
|
362
|
+
$log.info("Server shutting down…")
|
363
|
+
|
364
|
+
listener.stop if listener
|
365
|
+
|
366
|
+
if websocket_server
|
367
|
+
if config['open_url'] && File.executable?('/usr/bin/open')
|
368
|
+
websocket_server.broadcast('close')
|
369
|
+
end
|
370
|
+
|
371
|
+
websocket_server.shutdown
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
$log = Logger.new(STDERR)
|
4
|
+
$log.formatter = proc do |severity, datetime, progname, msg|
|
5
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')}] [#{Process.pid}] %7s #{msg}\n" % "[#{severity}]"
|
6
|
+
end
|
7
|
+
|
8
|
+
class Profiler
|
9
|
+
@@instance = nil
|
10
|
+
|
11
|
+
def initialize(format = "Program ran in %.3f seconds")
|
12
|
+
@current = Entry.new(format)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.enabled=(flag)
|
16
|
+
@@instance.dump if @@instance
|
17
|
+
@@instance = flag ? Profiler.new : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.enabled
|
21
|
+
@@instance ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.run(action, &block)
|
25
|
+
if @@instance
|
26
|
+
@@instance.profile("#{action} took %.3f seconds", &block)
|
27
|
+
else
|
28
|
+
block.call
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.group(group, &block)
|
33
|
+
if @@instance
|
34
|
+
@@instance.profile_group(group, &block)
|
35
|
+
else
|
36
|
+
block.call
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def profile(format)
|
41
|
+
parent = @current
|
42
|
+
parent.add_child(@current = Entry.new(format))
|
43
|
+
res = yield
|
44
|
+
@current.finished!
|
45
|
+
@current = parent
|
46
|
+
res
|
47
|
+
end
|
48
|
+
|
49
|
+
def profile_group(group)
|
50
|
+
@current.group(group) do
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def dump
|
56
|
+
@current.dump
|
57
|
+
end
|
58
|
+
|
59
|
+
class Entry
|
60
|
+
attr_reader :duration
|
61
|
+
|
62
|
+
def initialize(format)
|
63
|
+
@format = format
|
64
|
+
@start = Time.now
|
65
|
+
end
|
66
|
+
|
67
|
+
def group(name)
|
68
|
+
@groups ||= {}
|
69
|
+
@groups[name] ||= { :duration => 0, :count => 0 }
|
70
|
+
|
71
|
+
previous_group, @current_group = @current_group, name
|
72
|
+
|
73
|
+
start = Time.now
|
74
|
+
res = yield
|
75
|
+
@groups[name][:duration] += Time.now - start
|
76
|
+
@groups[name][:count] += 1
|
77
|
+
|
78
|
+
@groups[previous_group][:duration] -= Time.now - start if previous_group
|
79
|
+
@current_group = previous_group
|
80
|
+
|
81
|
+
res
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_child(child)
|
85
|
+
@children ||= []
|
86
|
+
@children << child
|
87
|
+
end
|
88
|
+
|
89
|
+
def finished!
|
90
|
+
@duration ||= Time.now - @start
|
91
|
+
end
|
92
|
+
|
93
|
+
def indent(level)
|
94
|
+
' ' * level
|
95
|
+
end
|
96
|
+
|
97
|
+
def dump(level = 0)
|
98
|
+
self.finished!
|
99
|
+
|
100
|
+
STDERR.puts indent(level) + (@format % @duration)
|
101
|
+
|
102
|
+
if @groups
|
103
|
+
@groups.sort_by { |group, info| info[:duration] }.reverse.each do |group, info|
|
104
|
+
STDERR.puts indent(level+1) + "[#{group}: %.3f seconds, called #{info[:count]} time(s), %.3f seconds/time]" % [ info[:duration], info[:duration] / info[:count] ]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if @children
|
109
|
+
@children.sort_by { |child| child.duration }.reverse.each do |child|
|
110
|
+
child.dump(level + 1)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: glim
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Allan Odgaard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mercenary
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: liquid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: kramdown
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.14'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: listen
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: websocket
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mime-types
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.2'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: glim-sass-converter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.1'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: glim-seo-tag
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.1'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: glim-feed
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.1'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
executables:
|
142
|
+
- glim
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- bin/glim
|
147
|
+
- lib/cache.rb
|
148
|
+
- lib/commands.rb
|
149
|
+
- lib/exception.rb
|
150
|
+
- lib/liquid_ext.rb
|
151
|
+
- lib/local_server.rb
|
152
|
+
- lib/log_and_profile.rb
|
153
|
+
- lib/version.rb
|
154
|
+
homepage: https://macromates.com/glim/
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.7.6
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Static site generator inspired by Jekyll but a lot faster
|
178
|
+
test_files: []
|