widow 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/widow +11 -5
  3. data/lib/widow.rb +209 -158
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35ef838be5110f77688f4f4cb226ac2635d7daba
4
- data.tar.gz: 64948953b16fe1a60bcf135089a480e4becbe836
3
+ metadata.gz: 20d7246686fc97b87af58bfc06e60321bd923037
4
+ data.tar.gz: 62ff7b3c5921f3696faeb9e35a34a6c5fa2ef171
5
5
  SHA512:
6
- metadata.gz: f7e929dcb1861b2c5389f5aea8e11a7d9d99ee9a8fce67d98826d6264b8d4a438411005e346318c80e1f6b3c54ee289de0bd282b7457596dbe5cdcfa1e222836
7
- data.tar.gz: bea70a334096a54e2334aeea16c98731aa18a7b2d341425672cf91e8be22c215ac856f7609bf38663ab145003f2292ad2716bd839b585a726b1466542064444b
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.new.cut options
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.new.spin options
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
- class Widow
9
- class CompileError < Exception
10
- def initialize filename, exception
11
- super "#{filename} failed to compile:\n#{exception.message}\n\t#{exception.backtrace.join "\n\t"}"
12
- end
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
- exclude = Exclude.new from
20
-
21
- Find.find from do |path|
22
- path = Pathname.new path
23
- next if path == from
24
- if exclude.ingnored? path
25
- log info: "#{path} ignored"
26
- next
27
- end
28
- begin
29
- make from, path.relative_path_from(from), to
30
- rescue CompileError
31
- end
32
- end
33
- return
34
- end
35
-
36
- def spin options = {}
37
- options = {from: Pathname.pwd, to: Pathname.pwd, port: 8888}.merge options
38
- from = Pathname.new(options[:from])
39
- to = Pathname.new(options[:to])
40
- exclude = Exclude.new from
41
-
42
- allowed_templates = Tilt.lazy_map.map do |ext, _|
43
- begin
44
- Tilt[ext]
45
- next ext
46
- rescue LoadError
47
- next
48
- end
49
- end.compact
50
-
51
- Rack::Handler.default.run(
52
- Proc.new do |env|
53
- name = Pathname.new env["PATH_INFO"][1..-1]
54
- log info: "#{name} requested"
55
- file = bin + name
56
- original = src + name
57
- unless original.exist?
58
- matching_templates = allowed_templates.select do |ext|
59
- next Tilt[ext].metadata[:mime_type] == case name.extname[1..-1]
60
- when 'js' then 'application/javascript'
61
- when 'css' then 'text/css'
62
- when 'html' then 'text/html'
63
- end
64
- end
65
- original = Pathname.glob(src + "#{name.basename name.extname}.{#{matching_templates.join ?,}}").first
66
- end
67
- next [404, {'Content-Type' => 'text/html'}, ["File not found!"]] if exclude.ingnored?(original) || original.nil? || !original.exist?
68
-
69
- begin
70
- make src, original.relative_path_from(src), bin if !file.exist? || file.mtime < original.mtime
71
- next [200, {'Content-Type' => 'text/html'}, [file.file? ? file.read : '']]
72
- rescue CompileError => e
73
- next [502, {'Content-Type' => 'text/html'}, [e.message]]
74
- end
75
- end,
76
- Port: options[:port]
77
- )
78
- return
79
- end
80
-
81
- private
82
- class Exclude
83
- def initialize root_dir
84
- @patterns = Pathname.glob("#{root_dir}/**/.widowignore").map do |file|
85
- next [file, File.readlines(file)]
86
- end.to_h.map do |file, lines|
87
- lines.map do |line|
88
- /^#{file.dirname}\/#{line}/ unless line =~ /^\s*$/
89
- end.compact
90
- end.flatten(1) << /.*\/\.widowignore$/
91
- end
92
-
93
- def ingnored? file
94
- return @patterns.any? do |pattern|
95
- file.to_s =~ pattern
96
- end
97
- end
98
- end
99
-
100
- # proxies or renders a file based on it's properties and tilt capacity
101
- # throws a CompileError when the file fails to compile
102
- def make root, file, to
103
- unless Tilt.registered? file.extname[1..-1].to_s
104
- proxy root, file, to
105
- return
106
- end
107
-
108
- begin
109
- render root, file, to
110
- rescue LoadError
111
- log warning: "#{file} has a Tilt template that cannot be loaded"
112
- proxy root, file, to
113
- rescue CompileError => e
114
- log error: e
115
- raise e
116
- end
117
- end
118
-
119
- # creates a carbon copy of a file elsewhere
120
- def proxy root, file, to
121
- to.mkpath unless to.exist?
122
- obj = to + file
123
- src = root + file
124
- return if src == obj
125
- if src.directory?
126
- obj.mkpath unless obj.exist?
127
- elsif src.file?
128
- FileUtils.cp src, obj
129
- end
130
- log info: "\t#{src} => #{src}"
131
- end
132
-
133
- # Takes a file and tries to render it through tilt to the destination directory
134
- # Throws load error when tilt template is not resolvable
135
- # Throws a BuildError when on an internal error
136
- def render root, file, to
137
- tilt = Tilt.new root + file
138
- type = case tilt.metadata[:mime_type]
139
- when 'application/javascript' then 'js'
140
- when 'text/css' then 'css'
141
- else 'html'
142
- end
143
-
144
- begin
145
- dest = to + "#{file.basename file.extname}.#{type}"
146
- File.write dest, tilt.render
147
- log info: "\t#{tilt.file} => #{dest}"
148
- rescue LoadError => e
149
- raise e
150
- rescue Exception => e
151
- raise CompileError.new root + file, e
152
- end
153
- end
154
-
155
- def log line
156
- line.each do |level , string|
157
- puts "#{level}: #{string}"
158
- end
159
- end
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.0
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-03-14 00:00:00.000000000 Z
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.7
99
+ rubygems_version: 2.6.8
100
100
  signing_key:
101
101
  specification_version: 4
102
102
  summary: Spins web superlanguages as you write!