mill 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/TODO.txt +38 -0
- data/lib/mill.rb +336 -0
- data/lib/mill/file_types.rb +30 -0
- data/lib/mill/html_helpers.rb +166 -0
- data/lib/mill/navigator.rb +84 -0
- data/lib/mill/resource.rb +116 -0
- data/lib/mill/resources/feed.rb +63 -0
- data/lib/mill/resources/generic.rb +15 -0
- data/lib/mill/resources/image.rb +36 -0
- data/lib/mill/resources/redirect.rb +36 -0
- data/lib/mill/resources/robots.rb +25 -0
- data/lib/mill/resources/sitemap.rb +35 -0
- data/lib/mill/resources/text.rb +157 -0
- data/lib/mill/schemas/atom.xsd +244 -0
- data/lib/mill/schemas/sitemap.xsd +116 -0
- data/lib/mill/tasks.rake +31 -0
- data/lib/mill/version.rb +5 -0
- data/mill.gemspec +37 -0
- metadata +247 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8f1e09004e5ec42eaa48258220004b0821601096
|
4
|
+
data.tar.gz: 6e92566945ca1b4549feeb5496aafd3c964b067d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6b982c3c4fc9e81b47b50a303a15ccfb0cf4c3409a25b6e47e1aff213b3c08ec973329b069dd10693186c45d5df1824e173839ca1a251c48c7d44a597a6884b
|
7
|
+
data.tar.gz: 992d9f6ba951a49e12122e0a75fb09f2cd8932878dc1f9975decc590514874ea83587a7b211ea8d64de1f520d4af3ee3dff488f55cd84e28983f2adfa28b16b7
|
data/.gitignore
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.DS_Store
|
4
|
+
/.config
|
5
|
+
/coverage/
|
6
|
+
/InstalledFiles
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/test/site
|
12
|
+
/test/resources
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
## Specific to RubyMotion:
|
16
|
+
.dat*
|
17
|
+
.repl_history
|
18
|
+
build/
|
19
|
+
|
20
|
+
## Documentation cache and generated files:
|
21
|
+
/.yardoc/
|
22
|
+
/_yardoc/
|
23
|
+
/doc/
|
24
|
+
/rdoc/
|
25
|
+
|
26
|
+
## Environment normalisation:
|
27
|
+
/.bundle/
|
28
|
+
/lib/bundler/man/
|
29
|
+
|
30
|
+
# for a library or gem, you might want to ignore these files since the code is
|
31
|
+
# intended to run in multiple environments; otherwise, check them in:
|
32
|
+
Gemfile.lock
|
33
|
+
# .ruby-version
|
34
|
+
# .ruby-gemset
|
35
|
+
|
36
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
37
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 jslabovitz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/TODO.txt
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
- resolve confusion between content-generation in #load vs. #final_content
|
2
|
+
+ who does what?
|
3
|
+
+ replacement/modification of HTML elements (image sizing, etc.) only happens in #load
|
4
|
+
+ maybe #final_content should have option to fully qualify URIs, etc., to handle feed content
|
5
|
+
|
6
|
+
- split Resource#date into Resource#published & Resource#updated
|
7
|
+
+ add <published> element to feed
|
8
|
+
+ #published should be stated date (e.g., from header)
|
9
|
+
+ #updated should be mtime of source file
|
10
|
+
|
11
|
+
- refactor MIME type usage
|
12
|
+
+ allow specified type as regexp or glob (use File.fnmatch?)
|
13
|
+
+ convert to MIME::Type object(s)
|
14
|
+
|
15
|
+
- make Navigator into more generic Collection
|
16
|
+
+ include Enumerable
|
17
|
+
|
18
|
+
- make Resource::External for external links?
|
19
|
+
+ add on import of HTML (by examining href/src/etc attributes)
|
20
|
+
|
21
|
+
- Rationalize resource URI usage:
|
22
|
+
+ #shorten_uris should only apply to writing final files.
|
23
|
+
+ In original content/etc., URIs should be more flexible.
|
24
|
+
+ A #convert_uris method should convert all references to proper type.
|
25
|
+
|
26
|
+
- Compress/minify Javascript, CSS, and HTML
|
27
|
+
+ https://remino.net/rails-html-css-js-gzip-compression/
|
28
|
+
+ http://sass-lang.com/documentation/file.SASS_REFERENCE.html#_16
|
29
|
+
+ JS: https://github.com/lautis/uglifier
|
30
|
+
+ HTML: https://github.com/paolochiodi/htmlcompressor
|
31
|
+
+ CSS: https://github.com/matthiassiegel/cssminify
|
32
|
+
|
33
|
+
- Write compressed versions along with non-compressed
|
34
|
+
+ Modify SimpleServer to use compressed versions if asked by client
|
35
|
+
|
36
|
+
- Add MailChimp signup form generator to HTMLHelpers.
|
37
|
+
|
38
|
+
- Add Google Analytics bug generator to HTMLHelpers and Mill setup.
|
data/lib/mill.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'addressable'
|
2
|
+
require 'image_size'
|
3
|
+
require 'kramdown'
|
4
|
+
require 'mime/types'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'path'
|
7
|
+
require 'pp'
|
8
|
+
require 'RedCloth'
|
9
|
+
require 'rubypants'
|
10
|
+
require 'simple-server'
|
11
|
+
require 'time'
|
12
|
+
require 'tidy_ffi'
|
13
|
+
require 'term/ansicolor'
|
14
|
+
|
15
|
+
require 'mill/file_types'
|
16
|
+
require 'mill/html_helpers'
|
17
|
+
require 'mill/navigator'
|
18
|
+
require 'mill/resource'
|
19
|
+
require 'mill/resources/feed'
|
20
|
+
require 'mill/resources/generic'
|
21
|
+
require 'mill/resources/image'
|
22
|
+
require 'mill/resources/redirect'
|
23
|
+
require 'mill/resources/robots'
|
24
|
+
require 'mill/resources/sitemap'
|
25
|
+
require 'mill/resources/text'
|
26
|
+
require 'mill/version'
|
27
|
+
|
28
|
+
class Mill
|
29
|
+
|
30
|
+
attr_accessor :input_dir
|
31
|
+
attr_accessor :output_dir
|
32
|
+
attr_accessor :site_title
|
33
|
+
attr_accessor :site_uri
|
34
|
+
attr_accessor :site_email
|
35
|
+
attr_accessor :site_control_date
|
36
|
+
attr_accessor :feed_resource
|
37
|
+
attr_accessor :sitemap_resource
|
38
|
+
attr_accessor :robots_resource
|
39
|
+
attr_accessor :ssh_location
|
40
|
+
attr_accessor :beta_ssh_location
|
41
|
+
attr_accessor :resources
|
42
|
+
attr_accessor :shorten_uris
|
43
|
+
attr_accessor :navigator
|
44
|
+
attr_accessor :navigator_items
|
45
|
+
attr_accessor :resource_classes
|
46
|
+
attr_accessor :schema_types
|
47
|
+
attr_accessor :redirects
|
48
|
+
attr_accessor :input_file_type_order
|
49
|
+
attr_accessor :link_elem_attrs
|
50
|
+
|
51
|
+
DefaultResourceClasses = [
|
52
|
+
Resource::Text,
|
53
|
+
Resource::Image,
|
54
|
+
Resource::Generic,
|
55
|
+
]
|
56
|
+
|
57
|
+
SchemasDir = Path.new(__FILE__).dirname / 'mill' / 'schemas'
|
58
|
+
|
59
|
+
DefaultSchemaTypes = {
|
60
|
+
feed: SchemasDir / 'atom.xsd',
|
61
|
+
sitemap: SchemasDir / 'sitemap.xsd',
|
62
|
+
}
|
63
|
+
|
64
|
+
def initialize(params={})
|
65
|
+
@resource_classes = {}
|
66
|
+
@resources = []
|
67
|
+
@resources_by_uri = {}
|
68
|
+
@schema_types = {}
|
69
|
+
@schemas = {}
|
70
|
+
@shorten_uris = true
|
71
|
+
@input_file_type_order = [:generic, :image, :text]
|
72
|
+
@link_elem_attrs = %w{
|
73
|
+
img/@src
|
74
|
+
script/@src
|
75
|
+
a/@href
|
76
|
+
link/@href
|
77
|
+
stylesheet/@href
|
78
|
+
}
|
79
|
+
params.each { |k, v| send("#{k}=", v) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def input_dir=(path)
|
83
|
+
@input_dir = Path.new(path).expand_path
|
84
|
+
end
|
85
|
+
|
86
|
+
def output_dir=(path)
|
87
|
+
@output_dir = Path.new(path).expand_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def site_uri=(uri)
|
91
|
+
@site_uri = Addressable::URI.parse(uri)
|
92
|
+
end
|
93
|
+
|
94
|
+
def site_control_date=(date)
|
95
|
+
begin
|
96
|
+
@site_control_date = Date.parse(date)
|
97
|
+
rescue ArgumentError => e
|
98
|
+
raise "bad control date #{date.inspect}: #{e}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def file_type(file)
|
103
|
+
if file.directory? || file.basename.to_s[0] == '.'
|
104
|
+
return :ignore
|
105
|
+
else
|
106
|
+
MIME::Types.of(file.to_s).each do |mime_type|
|
107
|
+
if (type = @file_types[mime_type.content_type])
|
108
|
+
return type
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_resource(resource)
|
116
|
+
resource.mill = self
|
117
|
+
begin
|
118
|
+
# ;;warn "loading #{resource.class.type} resource #{resource.uri} as #{resource.class}"
|
119
|
+
resource.load
|
120
|
+
rescue => e
|
121
|
+
warn "Failed to load resource #{resource.uri} (#{resource.class}): #{e}"
|
122
|
+
raise
|
123
|
+
end
|
124
|
+
@resources << resource
|
125
|
+
end
|
126
|
+
|
127
|
+
def update_resource(resource)
|
128
|
+
@resources_by_uri[resource.uri] = resource
|
129
|
+
end
|
130
|
+
|
131
|
+
def find_resource(uri)
|
132
|
+
uri = Addressable::URI.parse(uri.to_s) unless uri.kind_of?(Addressable::URI)
|
133
|
+
resource = @resources_by_uri[uri]
|
134
|
+
if resource.nil? && @shorten_uris
|
135
|
+
uri.path = uri.path.sub(%r{\.html$}, '')
|
136
|
+
resource = @resources_by_uri[uri]
|
137
|
+
end
|
138
|
+
resource
|
139
|
+
end
|
140
|
+
|
141
|
+
def home_resource
|
142
|
+
find_resource('/') or raise "Can't find home"
|
143
|
+
end
|
144
|
+
|
145
|
+
def schema_for_type(type)
|
146
|
+
@schemas[type]
|
147
|
+
end
|
148
|
+
|
149
|
+
def tag_uri
|
150
|
+
"tag:#{@site_uri.host.downcase},#{@site_control_date}:"
|
151
|
+
end
|
152
|
+
|
153
|
+
def feed_generator
|
154
|
+
[
|
155
|
+
'Mill',
|
156
|
+
{
|
157
|
+
uri: Addressable::URI.parse('http://github.com/jslabovitz/mill'),
|
158
|
+
version: Mill::VERSION,
|
159
|
+
}
|
160
|
+
]
|
161
|
+
end
|
162
|
+
|
163
|
+
def feed_author_name
|
164
|
+
@site_title
|
165
|
+
end
|
166
|
+
|
167
|
+
def feed_author_uri
|
168
|
+
@site_uri
|
169
|
+
end
|
170
|
+
|
171
|
+
def feed_author_email
|
172
|
+
@site_email
|
173
|
+
end
|
174
|
+
|
175
|
+
def public_resources
|
176
|
+
@resources.select(&:public)
|
177
|
+
end
|
178
|
+
|
179
|
+
def clean
|
180
|
+
@output_dir.rmtree if @output_dir.exist?
|
181
|
+
@output_dir.mkpath
|
182
|
+
end
|
183
|
+
|
184
|
+
def load
|
185
|
+
warn "loading resources..."
|
186
|
+
build_file_types
|
187
|
+
build_resource_classes
|
188
|
+
build_schemas
|
189
|
+
load_files
|
190
|
+
load_others
|
191
|
+
end
|
192
|
+
|
193
|
+
def load_others
|
194
|
+
make_redirects
|
195
|
+
make_feed
|
196
|
+
make_sitemap
|
197
|
+
make_robots
|
198
|
+
make_navigator
|
199
|
+
end
|
200
|
+
|
201
|
+
def build
|
202
|
+
warn "building #{@resources.length} resources..."
|
203
|
+
@resources.each do |resource|
|
204
|
+
begin
|
205
|
+
resource.build
|
206
|
+
rescue => e
|
207
|
+
warn "Failed to build resource #{resource.uri}: #{e}"
|
208
|
+
raise
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def publish(mode=:final)
|
214
|
+
location = case mode
|
215
|
+
when :final
|
216
|
+
@ssh_location
|
217
|
+
when :beta
|
218
|
+
@beta_ssh_location
|
219
|
+
else
|
220
|
+
raise "Unknown publish mode: #{mode.inspect}"
|
221
|
+
end
|
222
|
+
raise "Must specify SSH location" unless location
|
223
|
+
system('rsync',
|
224
|
+
# '--dry-run',
|
225
|
+
'--archive',
|
226
|
+
'--delete-after',
|
227
|
+
'--progress',
|
228
|
+
# '--verbose',
|
229
|
+
@output_dir.to_s + '/',
|
230
|
+
location,
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
def server
|
235
|
+
SimpleServer.run!(
|
236
|
+
root: @output_dir,
|
237
|
+
multihosting: false,
|
238
|
+
)
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def load_files
|
244
|
+
input_files_by_type.each do |type, input_files|
|
245
|
+
input_files.each do |input_file|
|
246
|
+
resource_class = @resource_classes[type] or raise "No resource class for #{input_file}"
|
247
|
+
resource = resource_class.new(
|
248
|
+
input_file: input_file,
|
249
|
+
output_file: @output_dir / input_file.relative_to(@input_dir))
|
250
|
+
add_resource(resource)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def input_files_by_type
|
256
|
+
hash = {}
|
257
|
+
raise "Input path not found: #{@input_dir}" unless @input_dir.exist?
|
258
|
+
@input_dir.find do |input_file|
|
259
|
+
input_file = @input_dir / input_file
|
260
|
+
type = file_type(input_file) or raise "Can't determine file type of #{input_file}"
|
261
|
+
unless type == :ignore
|
262
|
+
hash[type] ||= []
|
263
|
+
hash[type] << input_file
|
264
|
+
end
|
265
|
+
end
|
266
|
+
hash.sort_by { |t, f| input_file_type_order.index(t) || input_file_type_order.length }
|
267
|
+
end
|
268
|
+
|
269
|
+
def make_feed
|
270
|
+
@feed_resource = Resource::Feed.new(
|
271
|
+
output_file: @output_dir / 'feed.xml')
|
272
|
+
add_resource(@feed_resource)
|
273
|
+
end
|
274
|
+
|
275
|
+
def make_sitemap
|
276
|
+
@sitemap_resource = Resource::Sitemap.new(
|
277
|
+
output_file: @output_dir / 'sitemap.xml')
|
278
|
+
add_resource(@sitemap_resource)
|
279
|
+
end
|
280
|
+
|
281
|
+
def make_robots
|
282
|
+
@robots_resource = Resource::Robots.new(
|
283
|
+
output_file: @output_dir / 'robots.txt')
|
284
|
+
add_resource(@robots_resource)
|
285
|
+
end
|
286
|
+
|
287
|
+
def make_navigator
|
288
|
+
if @navigator_items
|
289
|
+
@navigator = Navigator.new
|
290
|
+
@navigator.items = @navigator_items.map do |uri, title|
|
291
|
+
uri = Addressable::URI.parse(uri)
|
292
|
+
if title.nil? && uri.relative?
|
293
|
+
resource = find_resource(uri) or raise "Can't find navigation resource for URI #{uri}"
|
294
|
+
title = resource.title
|
295
|
+
end
|
296
|
+
Navigator::Item.new(uri: uri, title: title)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def make_redirects
|
302
|
+
return unless @redirects
|
303
|
+
@redirects.each do |from, to|
|
304
|
+
output_file = @output_dir / Path.new(from).relative_to('/')
|
305
|
+
resource = Resource::Redirect.new(
|
306
|
+
output_file: output_file,
|
307
|
+
redirect_uri: to)
|
308
|
+
add_resource(resource)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def build_schemas
|
313
|
+
DefaultSchemaTypes.merge(@schema_types).each do |type, file|
|
314
|
+
;;warn "loading schema #{type} from #{file}"
|
315
|
+
@schemas[type] = Nokogiri::XML::Schema(file.open) { |c| c.strict.nonet }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def build_file_types
|
320
|
+
@file_types = {}
|
321
|
+
FileTypes.each do |type, mime_types|
|
322
|
+
mime_types.each do |mime_type|
|
323
|
+
MIME::Types[mime_type].each do |t|
|
324
|
+
@file_types[t.content_type] = type
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def build_resource_classes
|
331
|
+
@resource_classes = Hash[
|
332
|
+
(DefaultResourceClasses + @resource_classes).map { |rc| [rc.type, rc] }
|
333
|
+
]
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|