widow 0.2.0 → 0.2.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 +4 -4
- data/bin/widow +11 -5
- data/lib/widow.rb +209 -158
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20d7246686fc97b87af58bfc06e60321bd923037
|
4
|
+
data.tar.gz: 62ff7b3c5921f3696faeb9e35a34a6c5fa2ef171
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a99ca1e7844279c441093f964b4c7f4b11a86359fe5d58828b7fcd3d5e62f68a449dd2f0d255d0ea9c3a6477cd68fcc8fb35283689436e6dc6127fb9627be136
|
7
|
+
data.tar.gz: 19fd974e4a248a8746d6354ae0f50043afb9710dd73e07d3013b1e9c325dd0e5b642c45c508dbe1d9c4830d8170e62d5b286b9352e2712208f33c49d6369651a
|
data/bin/widow
CHANGED
@@ -3,21 +3,27 @@ require 'thor'
|
|
3
3
|
require 'widow'
|
4
4
|
|
5
5
|
class Spiderling < Thor
|
6
|
+
class_option :debug, type: :boolean, desc: "enables debug logging"
|
7
|
+
|
6
8
|
desc "cut", "Compile files from current directory into current directory. Copies files that cannot be processed by tilt"
|
7
9
|
method_option :from, type: :string, desc: "Defaults to current directory"
|
8
10
|
method_option :to, type: :string, desc: "Defaults to current directory"
|
9
|
-
def cut
|
10
|
-
Widow.
|
11
|
+
def cut
|
12
|
+
Widow.perform :cut, symbolise(options)
|
11
13
|
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
15
|
desc "spin", "Start a webserver on localhost and serve files from the destination forlder, compiling them as necessary"
|
16
16
|
method_option :from, type: :string, desc: "Defaults to current directory"
|
17
17
|
method_option :to, type: :string, desc: "Defaults to current directory"
|
18
18
|
method_option :port, type: :numeric, desc: "Defaults to 8888"
|
19
|
+
method_option :serve_source, type: :boolean, desc: "Serves source files if they are asked for directly, even if they are ignored, off by default"
|
19
20
|
def spin
|
20
|
-
Widow.
|
21
|
+
Widow.perform :spin, symbolise(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def symbolise options
|
26
|
+
return options.map { |k, v| [k.to_sym, v] }.to_h
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
data/lib/widow.rb
CHANGED
@@ -1,160 +1,211 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'tilt'
|
3
|
-
require 'find'
|
4
|
-
require 'pathname'
|
5
|
-
require 'fileutils'
|
6
|
-
require 'rack'
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
require 'optparse'
|
2
|
+
require 'tilt'
|
3
|
+
require 'find'
|
4
|
+
require 'pathname'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rack'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
class Widow
|
10
|
+
@@mime_map = [
|
11
|
+
['js', 'application/javascript'],
|
12
|
+
['css', 'text/css'],
|
13
|
+
['html', 'text/html']
|
14
|
+
]
|
15
|
+
|
16
|
+
class CompileError < Exception
|
17
|
+
def initialize tilt, exception
|
18
|
+
super "#{tilt.file} with template #{tilt} failed to compile:\n#{exception.message}\n\t#{exception.backtrace.join "\n\t"}"
|
19
|
+
end
|
13
20
|
end
|
14
|
-
|
21
|
+
|
22
|
+
def self.perform method, options
|
23
|
+
widow = Widow.new(options)
|
24
|
+
widow.public_send(method, options)
|
25
|
+
widow.kill
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize options = {}
|
29
|
+
log_level = options.delete(:debug) ? :debug : :info
|
30
|
+
@logger = Logger.new(STDOUT, level: log_level)
|
31
|
+
@logger.info "log level #{log_level}"
|
32
|
+
end
|
33
|
+
|
15
34
|
def cut options = {}
|
16
|
-
options = {from: Pathname.pwd, to: Pathname.pwd}.merge options
|
17
|
-
from = Pathname.new(options[:from])
|
18
|
-
to = Pathname.new(options[:to])
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
if
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
35
|
+
options = {from: Pathname.pwd, to: Pathname.pwd}.merge options
|
36
|
+
from = Pathname.new(options[:from])
|
37
|
+
to = Pathname.new(options[:to])
|
38
|
+
@logger.info "cut from #{from} to #{to}"
|
39
|
+
exclude = Exclude.new from, @logger
|
40
|
+
|
41
|
+
Find.find from do |path|
|
42
|
+
path = Pathname.new path
|
43
|
+
next if path == from
|
44
|
+
if exclude.ignored? path
|
45
|
+
@logger.info "#{path} ignored"
|
46
|
+
next
|
47
|
+
end
|
48
|
+
begin
|
49
|
+
make from, path.relative_path_from(from), to
|
50
|
+
rescue CompileError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
def spin options = {}
|
57
|
+
options = {from: Pathname.pwd, to: Pathname.pwd, port: 8888, serve_source: false}.merge options
|
58
|
+
from = Pathname.new(options[:from])
|
59
|
+
to = Pathname.new(options[:to])
|
60
|
+
@logger.info "spin from #{from} to #{to}"
|
61
|
+
@logger.info "port #{options[:port]}"
|
62
|
+
@logger.info "serve_source = #{options[:serve_source]}"
|
63
|
+
exclude = Exclude.new from, @logger
|
64
|
+
|
65
|
+
mime_to_ext = Hash.new { |hash, key| hash[key] = [] }
|
66
|
+
Tilt.lazy_map.map do |ext, _|
|
67
|
+
begin
|
68
|
+
tilt = Tilt[ext]
|
69
|
+
@logger.info "loaded template #{tilt} for extention #{ext}"
|
70
|
+
next [tilt.metadata[:mime_type] || tilt.default_mime_type, ext]
|
71
|
+
rescue LoadError
|
72
|
+
next
|
73
|
+
end
|
74
|
+
end.compact.each do | mime_type, ext |
|
75
|
+
mime_to_ext[mime_type] << ext
|
76
|
+
end
|
77
|
+
|
78
|
+
Rack::Handler.default.run(
|
79
|
+
Proc.new do |env|
|
80
|
+
@logger.debug "#{env["PATH_INFO"]} requested"
|
81
|
+
|
82
|
+
file = Pathname.new env["PATH_INFO"][1..-1]
|
83
|
+
file += 'index.html' if (from + file).directory?
|
84
|
+
search = from + file.dirname + "#{file.basename file.extname}.{#{(mime_to_ext[mime_type file] + [file.extname[1..-1]]).join ?,}}"
|
85
|
+
@logger.debug "searching for \'#{search}\'"
|
86
|
+
original = Pathname.glob(search).first
|
87
|
+
file = to + file
|
88
|
+
|
89
|
+
if (exclude.ignored?(original) && !(options[:serve_source] && original.basename == file.basename))
|
90
|
+
@logger.info "#{file} ignored"
|
91
|
+
next file_not_found
|
92
|
+
elsif (original.nil? || !original.exist?)
|
93
|
+
@logger.debug "source not found"
|
94
|
+
next file_not_found
|
95
|
+
end
|
96
|
+
@logger.debug "#{original} found"
|
97
|
+
|
98
|
+
begin
|
99
|
+
if !file.exist? || file.mtime < original.mtime
|
100
|
+
if original.basename == file.basename
|
101
|
+
proxy from, original.relative_path_from(from), to
|
102
|
+
else
|
103
|
+
make from, original.relative_path_from(from), to
|
104
|
+
end
|
105
|
+
end
|
106
|
+
next [200, {'Content-Type' => mime_type(file)}, [file.file? ? file.read : '']]
|
107
|
+
rescue CompileError => e
|
108
|
+
next [502, {'Content-Type' => 'text/html'}, [e.message]]
|
109
|
+
end
|
110
|
+
end,
|
111
|
+
Port: options[:port]
|
112
|
+
)
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
# Called to clean up
|
117
|
+
def kill
|
118
|
+
@logger.close
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
class Exclude
|
124
|
+
def initialize root_dir, logger
|
125
|
+
@patterns = Pathname.glob("#{root_dir}/**/.widowignore").map do |file|
|
126
|
+
next [file, File.readlines(file)]
|
127
|
+
end.to_h.map do |file, lines|
|
128
|
+
lines.map do |line|
|
129
|
+
/^#{file.dirname}\/#{line.sub /\s*$/, ''}/ unless line =~ /^\s*$/
|
130
|
+
end.compact
|
131
|
+
end.flatten(1) << /.*\/\.widowignore$/
|
132
|
+
logger.info "ignoring:\n#{to_s}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def ignored? file
|
136
|
+
return @patterns.any? do |pattern|
|
137
|
+
file.to_s =~ pattern
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def ignored_patterns
|
142
|
+
return @patterns.map &:to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_s
|
146
|
+
return ignored_patterns.join "\n"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# proxies or renders a file based on it's properties and tilt capacity
|
151
|
+
# throws a CompileError when the file fails to compile
|
152
|
+
def make root, file, to
|
153
|
+
unless Tilt.registered? file.extname[1..-1].to_s
|
154
|
+
proxy root, file, to
|
155
|
+
return
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
render root, file, to
|
160
|
+
rescue LoadError
|
161
|
+
@logger.warn "#{file} has a Tilt template that cannot be loaded"
|
162
|
+
proxy root, file, to
|
163
|
+
rescue CompileError => e
|
164
|
+
@logger.error e.to_s
|
165
|
+
raise e
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# creates a carbon copy of a file elsewhere
|
170
|
+
def proxy root, file, to
|
171
|
+
src = root + file
|
172
|
+
return if src.directory?
|
173
|
+
|
174
|
+
obj = to + file
|
175
|
+
obj.dirname.mkpath unless obj.dirname.exist?
|
176
|
+
|
177
|
+
FileUtils.cp src, obj
|
178
|
+
@logger.info "\t#{src} => #{obj}"
|
179
|
+
end
|
180
|
+
|
181
|
+
# Takes a file and tries to render it through tilt to the destination directory
|
182
|
+
# Throws load error when tilt template is not resolvable
|
183
|
+
# Throws a BuildError when on an internal error
|
184
|
+
def render root, file, to
|
185
|
+
tilt = Tilt.new root + file
|
186
|
+
|
187
|
+
begin
|
188
|
+
output = tilt.render
|
189
|
+
rescue Exception => e
|
190
|
+
raise CompileError.new tilt, e
|
191
|
+
end
|
192
|
+
|
193
|
+
dest = to + "#{file.basename file.extname}.#{default_ext tilt.metadata[:mime_type]}"
|
194
|
+
dest.dirname.mkpath unless dest.dirname.exist?
|
195
|
+
File.write(dest, output)
|
196
|
+
@logger.info "\t#{tilt.file} => #{dest}"
|
197
|
+
end
|
198
|
+
|
199
|
+
def default_ext mime_type
|
200
|
+
return (@@mime_map.find{ |e, t| t == mime_type } || ['html', nil])[0]
|
201
|
+
end
|
202
|
+
|
203
|
+
def mime_type filename
|
204
|
+
name = Pathname.new(filename).extname[1..-1]
|
205
|
+
return (@@mime_map.find{ |e, t| e == name } || [nil, 'text/plain'])[1]
|
206
|
+
end
|
207
|
+
|
208
|
+
def file_not_found
|
209
|
+
return [404, {'Content-Type' => 'text/html'}, ["File not found!"]]
|
210
|
+
end
|
211
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: widow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Victorovich Matveev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -96,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
96
|
version: '0'
|
97
97
|
requirements: []
|
98
98
|
rubyforge_project:
|
99
|
-
rubygems_version: 2.6.
|
99
|
+
rubygems_version: 2.6.8
|
100
100
|
signing_key:
|
101
101
|
specification_version: 4
|
102
102
|
summary: Spins web superlanguages as you write!
|