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.
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!