mill 0.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/.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
|