mill 0.16 → 0.18
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/LICENSE +1 -1
- data/Rakefile +1 -9
- data/TODO.md +4 -0
- data/bin/mill +1 -27
- data/lib/mill/command.rb +13 -0
- data/lib/mill/commands/build.rb +17 -0
- data/lib/mill/commands/check.rb +16 -0
- data/lib/mill/commands/diff.rb +18 -0
- data/lib/mill/commands/list.rb +20 -0
- data/lib/mill/commands/snapshot.rb +20 -0
- data/lib/mill/commands/tree.rb +17 -0
- data/lib/mill/commands/types.rb +18 -0
- data/lib/mill/commands/upload.rb +24 -0
- data/lib/mill/config.rb +27 -0
- data/lib/mill/resource.rb +74 -87
- data/lib/mill/resources/blob.rb +4 -0
- data/lib/mill/resources/feed.rb +5 -13
- data/lib/mill/resources/image.rb +10 -18
- data/lib/mill/resources/markdown.rb +20 -0
- data/lib/mill/resources/markup.rb +62 -0
- data/lib/mill/resources/page.rb +140 -0
- data/lib/mill/resources/redirect.rb +17 -16
- data/lib/mill/resources/robots.rb +3 -4
- data/lib/mill/resources/sitemap.rb +3 -5
- data/lib/mill/resources/stylesheet.rb +4 -4
- data/lib/mill/resources/textile.rb +27 -0
- data/lib/mill/resources.rb +89 -0
- data/lib/mill/site.rb +182 -283
- data/lib/mill.rb +27 -9
- data/mill.gemspec +19 -19
- data/test/content/c.md +4 -0
- data/test/content/d.md +4 -0
- data/test/main_test.rb +68 -0
- data/test/mill.yaml +8 -0
- metadata +118 -51
- data/Gemfile +0 -6
- data/TODO.txt +0 -61
- data/lib/mill/html_helpers.rb +0 -128
- data/lib/mill/navigator.rb +0 -61
- data/lib/mill/resources/dir.rb +0 -31
- data/lib/mill/resources/google_site_verification.rb +0 -30
- data/lib/mill/resources/text.rb +0 -218
- data/lib/mill/version.rb +0 -5
- data/test/test.rb +0 -56
data/lib/mill/html_helpers.rb
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
module HTMLHelpers
|
2
|
-
|
3
|
-
LinkElementsXPath = '//@href | //@src'
|
4
|
-
|
5
|
-
def html_document(type=:html4_transitional, &block)
|
6
|
-
doc = Nokogiri::HTML::Document.new
|
7
|
-
doc.encoding = 'UTF-8'
|
8
|
-
doc.internal_subset.remove
|
9
|
-
case type
|
10
|
-
when :html4_transitional
|
11
|
-
doc.create_internal_subset('html', '-//W3C//DTD HTML 4.01 Transitional//EN', 'http://www.w3.org/TR/html4/loose.dtd')
|
12
|
-
when :html5
|
13
|
-
doc.create_internal_subset('html', nil, nil)
|
14
|
-
else
|
15
|
-
raise "Unknown HTML type: #{type.inspect}"
|
16
|
-
end
|
17
|
-
Nokogiri::HTML::Builder.with(doc) do |doc|
|
18
|
-
yield(doc)
|
19
|
-
end
|
20
|
-
doc
|
21
|
-
end
|
22
|
-
|
23
|
-
def html_fragment(&block)
|
24
|
-
html = Nokogiri::HTML::DocumentFragment.parse('')
|
25
|
-
Nokogiri::HTML::Builder.with(html) do |html|
|
26
|
-
yield(html) if block_given?
|
27
|
-
end
|
28
|
-
html
|
29
|
-
end
|
30
|
-
|
31
|
-
def parse_html(str)
|
32
|
-
if str.strip.empty?
|
33
|
-
html = html_fragment
|
34
|
-
else
|
35
|
-
html = Nokogiri::HTML::Document.parse(str) { |config| config.strict }
|
36
|
-
check_errors(html)
|
37
|
-
end
|
38
|
-
html
|
39
|
-
end
|
40
|
-
|
41
|
-
def parse_html_fragment(str)
|
42
|
-
html = Nokogiri::HTML::DocumentFragment.parse(str) { |config| config.strict }
|
43
|
-
check_errors(html)
|
44
|
-
html
|
45
|
-
end
|
46
|
-
|
47
|
-
def check_errors(html)
|
48
|
-
html.errors.each do |error|
|
49
|
-
raise Mill::Error, "HTML error #{error}" unless error.message =~ /Tag .+? invalid$/
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def find_link_elements(html)
|
54
|
-
html.xpath(LinkElementsXPath)
|
55
|
-
end
|
56
|
-
|
57
|
-
def replace_element(html, xpath, &block)
|
58
|
-
html.xpath(xpath).each do |elem|
|
59
|
-
elem.replace(yield(elem))
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def google_analytics(tracker_id)
|
64
|
-
html_fragment do |html|
|
65
|
-
html.script(type: 'text/javascript') do
|
66
|
-
html << %Q{
|
67
|
-
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
68
|
-
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
69
|
-
}
|
70
|
-
end
|
71
|
-
html.script(type: 'text/javascript') do
|
72
|
-
html << %Q{
|
73
|
-
try {
|
74
|
-
var pageTracker = _gat._getTracker("#{tracker_id}");
|
75
|
-
pageTracker._trackPageview();
|
76
|
-
} catch(err) {}
|
77
|
-
}
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def link_if(state, html, &block)
|
83
|
-
elem = html_fragment { |h| yield(h) }
|
84
|
-
if state
|
85
|
-
html.a(href: uri) { html << elem.to_html }
|
86
|
-
else
|
87
|
-
html << elem.to_html
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
class PreText < String
|
92
|
-
|
93
|
-
def to_html
|
94
|
-
html_fragment do |html|
|
95
|
-
html.pre(self)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
class ::String
|
102
|
-
|
103
|
-
Converters = {
|
104
|
-
nil => RubyPants,
|
105
|
-
smart_quotes: RubyPants,
|
106
|
-
markdown: Kramdown::Document,
|
107
|
-
textile: [RedCloth, :no_span_caps],
|
108
|
-
pre: PreText,
|
109
|
-
}
|
110
|
-
|
111
|
-
def to_html(options={})
|
112
|
-
converter_class = Converters[options[:mode]] or raise "Unknown to_html mode: #{options[:mode].inspect}"
|
113
|
-
if converter_class.kind_of?(Array)
|
114
|
-
converter_class, *converter_options = *converter_class
|
115
|
-
converter = converter_class.new(self, converter_options)
|
116
|
-
else
|
117
|
-
converter = converter_class.new(self)
|
118
|
-
end
|
119
|
-
html = Nokogiri::HTML::DocumentFragment.parse(converter.to_html)
|
120
|
-
if !options[:multiline] && (p_elem = html.at_xpath('p'))
|
121
|
-
html = p_elem.children.to_html
|
122
|
-
end
|
123
|
-
html.to_html
|
124
|
-
end
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
end
|
data/lib/mill/navigator.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
module Mill
|
2
|
-
|
3
|
-
class Navigator
|
4
|
-
|
5
|
-
class Item
|
6
|
-
|
7
|
-
attr_accessor :uri
|
8
|
-
attr_accessor :title
|
9
|
-
|
10
|
-
def initialize(uri:, title: nil)
|
11
|
-
@uri = Addressable::URI.parse(uri)
|
12
|
-
@title = title
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(items: [], site: nil)
|
18
|
-
@items = Hash[
|
19
|
-
items.map do |uri, title|
|
20
|
-
item = Item.new(uri: uri, title: title)
|
21
|
-
[item.uri, item]
|
22
|
-
end
|
23
|
-
]
|
24
|
-
@site = site
|
25
|
-
end
|
26
|
-
|
27
|
-
def items
|
28
|
-
@items.values
|
29
|
-
end
|
30
|
-
|
31
|
-
def first_item
|
32
|
-
@items.values.first
|
33
|
-
end
|
34
|
-
|
35
|
-
def last_item
|
36
|
-
@items.values.last
|
37
|
-
end
|
38
|
-
|
39
|
-
def previous_item(uri)
|
40
|
-
if (item = @items[uri])
|
41
|
-
i = @items.values.index(item)
|
42
|
-
if i > 0
|
43
|
-
return @items.values[i - 1]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
nil
|
47
|
-
end
|
48
|
-
|
49
|
-
def next_item(uri)
|
50
|
-
if (item = @items[uri])
|
51
|
-
i = @items.values.index(item)
|
52
|
-
if i < @items.length - 1
|
53
|
-
return @items.values[i + 1]
|
54
|
-
end
|
55
|
-
end
|
56
|
-
nil
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
data/lib/mill/resources/dir.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Mill
|
2
|
-
|
3
|
-
class Resource
|
4
|
-
|
5
|
-
class Index < Text
|
6
|
-
|
7
|
-
include HTMLHelpers
|
8
|
-
|
9
|
-
attr_accessor :pages
|
10
|
-
|
11
|
-
def initialize(pages:, **args)
|
12
|
-
@pages = pages
|
13
|
-
super(**args)
|
14
|
-
end
|
15
|
-
|
16
|
-
def load
|
17
|
-
super
|
18
|
-
@content = html_fragment do |html|
|
19
|
-
html.dl do
|
20
|
-
@pages.each do |page|
|
21
|
-
html.dt(page.title)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Mill
|
2
|
-
|
3
|
-
class Resource
|
4
|
-
|
5
|
-
class GoogleSiteVerification < Resource
|
6
|
-
|
7
|
-
attr_accessor :key
|
8
|
-
|
9
|
-
def initialize(key:, **args)
|
10
|
-
@key = key
|
11
|
-
@public = false
|
12
|
-
super(**args)
|
13
|
-
end
|
14
|
-
|
15
|
-
def inspect
|
16
|
-
super + ", key: %p" % [
|
17
|
-
@key,
|
18
|
-
]
|
19
|
-
end
|
20
|
-
|
21
|
-
def load
|
22
|
-
@content = "google-site-verification: #{@key}.html\n"
|
23
|
-
super
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
data/lib/mill/resources/text.rb
DELETED
@@ -1,218 +0,0 @@
|
|
1
|
-
|
2
|
-
module Mill
|
3
|
-
|
4
|
-
class Resource
|
5
|
-
|
6
|
-
class Text < Resource
|
7
|
-
|
8
|
-
include HTMLHelpers
|
9
|
-
|
10
|
-
FileTypes = %w{
|
11
|
-
text/plain
|
12
|
-
text/html
|
13
|
-
text/markdown
|
14
|
-
}
|
15
|
-
|
16
|
-
attr_accessor :title
|
17
|
-
attr_accessor :summary
|
18
|
-
attr_accessor :author
|
19
|
-
attr_accessor :type
|
20
|
-
|
21
|
-
def initialize(title: nil, summary: nil, author: nil, public: true, output_file: nil, **args)
|
22
|
-
@title = title
|
23
|
-
@summary = summary
|
24
|
-
@author = author
|
25
|
-
@type = nil
|
26
|
-
super(
|
27
|
-
public: public,
|
28
|
-
output_file: output_file&.replace_extension('.html'),
|
29
|
-
**args)
|
30
|
-
if @path
|
31
|
-
@path.sub!(%r{\.html$}, '') if @site&.shorten_uris
|
32
|
-
@path.sub!(%r{(.*)index$}, '\1')
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def inspect
|
37
|
-
super + ", title: %p, summary: %p, author: %p, type: %p" % [
|
38
|
-
@title,
|
39
|
-
@summary,
|
40
|
-
@author,
|
41
|
-
@type,
|
42
|
-
]
|
43
|
-
end
|
44
|
-
|
45
|
-
def load
|
46
|
-
super
|
47
|
-
if @input_file
|
48
|
-
@content = @input_file.read
|
49
|
-
@type = case @input_file.extname.downcase
|
50
|
-
when '.md', '.mdown', '.markdown'
|
51
|
-
:markdown
|
52
|
-
when '.textile'
|
53
|
-
:textile
|
54
|
-
when '.txt'
|
55
|
-
:pre
|
56
|
-
when '.htm', '.html'
|
57
|
-
:html
|
58
|
-
else
|
59
|
-
raise "Unknown text type: #{@input_file}"
|
60
|
-
end
|
61
|
-
if @type != :html
|
62
|
-
parse_text_header
|
63
|
-
@content = (@content || '').to_html(mode: @type, multiline: true)
|
64
|
-
@type = :html
|
65
|
-
end
|
66
|
-
begin
|
67
|
-
@content = parse_html(@content)
|
68
|
-
rescue Error => e
|
69
|
-
raise e, "#{@input_file}: #{e}"
|
70
|
-
end
|
71
|
-
parse_html_header
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def parse_html_header
|
76
|
-
unless @title
|
77
|
-
if (title_elem = @content.at_xpath('/html/head/title'))
|
78
|
-
@title = title_elem.text
|
79
|
-
else
|
80
|
-
@title = @path
|
81
|
-
end
|
82
|
-
end
|
83
|
-
@content.xpath('/html/head/meta[@name]').each do |meta|
|
84
|
-
send("#{meta['name']}=", meta['content'])
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def parse_text_header
|
89
|
-
if @content.split(/\n/, 2).first =~ /^\w+:\s+/
|
90
|
-
header, @content = @content.split(/\n\n/, 2)
|
91
|
-
header.split(/\n/).map do |line|
|
92
|
-
key, value = line.strip.split(/:\s*/, 2)
|
93
|
-
key = key.gsub('-', '_').downcase.to_sym
|
94
|
-
send("#{key}=", value)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def final_content
|
100
|
-
html_document(@site&.html_version || :html5) do |doc|
|
101
|
-
doc.html(lang: 'en') do |html|
|
102
|
-
html.parent << head
|
103
|
-
html.parent << body
|
104
|
-
end
|
105
|
-
end.to_html
|
106
|
-
end
|
107
|
-
|
108
|
-
def head(&block)
|
109
|
-
html_fragment do |html|
|
110
|
-
html.head do
|
111
|
-
head = content_head
|
112
|
-
if (title = @title || (head && head.at_xpath('title')))
|
113
|
-
html.title { html << title.to_html }
|
114
|
-
end
|
115
|
-
yield(html) if block_given?
|
116
|
-
if head
|
117
|
-
head.children.reject { |e| e.text? || e.name == 'title' }.each do |e|
|
118
|
-
html << e.to_html
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def body(&block)
|
126
|
-
html_fragment do |html|
|
127
|
-
html.body do
|
128
|
-
if (elem = content_body)
|
129
|
-
html << elem.children.to_html
|
130
|
-
end
|
131
|
-
yield(html) if block_given?
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def content_head
|
137
|
-
@content && @content.at_xpath('/html/head')
|
138
|
-
end
|
139
|
-
|
140
|
-
def content_body
|
141
|
-
@content && @content.at_xpath('/html/body')
|
142
|
-
end
|
143
|
-
|
144
|
-
def add_external_link_targets(rel='noopener')
|
145
|
-
@content.xpath('//a').each do |a|
|
146
|
-
if a['href'] && a['href'] =~ /^\w+:/
|
147
|
-
a['target'] = '_blank'
|
148
|
-
a['rel'] = rel
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def remove_comments
|
154
|
-
@content.xpath('//comment()').each do |comment|
|
155
|
-
comment.remove
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def add_image_sizes
|
160
|
-
@content.xpath('//img').each do |img|
|
161
|
-
# skip elements that already have width/height defined
|
162
|
-
next if img[:width] || img[:height]
|
163
|
-
img_link = Addressable::URI.parse(img['src'])
|
164
|
-
raise Error, "No link in <img> element: #{img.to_s}" if img_link.nil? || img_link.empty?
|
165
|
-
next if img_link.host
|
166
|
-
img_uri = uri + img_link
|
167
|
-
img_resource = @site.find_resource(img_uri) or raise Error, "Can't find image for #{img_uri}"
|
168
|
-
img[:width], img[:height] = img_resource.width, img_resource.height
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def shorten_links
|
173
|
-
find_link_elements(@content).each do |attribute|
|
174
|
-
elem = attribute.parent
|
175
|
-
link_uri = Addressable::URI.parse(attribute.value) or raise Error, "Can't parse #{attribute.value.inspect} from #{xpath.inspect}"
|
176
|
-
link_uri = uri + link_uri
|
177
|
-
if link_uri.relative?
|
178
|
-
self_uri = uri.normalize
|
179
|
-
self_uri.scheme = 'http'
|
180
|
-
link_uri.scheme = 'http'
|
181
|
-
attribute.value = self_uri.route_to(link_uri)
|
182
|
-
# ;;warn "[#{path}] shortened link #{elem.name}/@#{attribute.name}: #{link_uri} => #{attribute.value}"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def summary
|
188
|
-
@summary || ((p = feed_content.at_xpath('//p')) && p.children)
|
189
|
-
end
|
190
|
-
|
191
|
-
def feed_content
|
192
|
-
if (body = content_body)
|
193
|
-
# If we have a main element (<div class="main"> or <main>), use that.
|
194
|
-
# Otherwise, use the body, but delete header/footer/nav divs or elements.
|
195
|
-
if (main = body.at_xpath('//div[@id="main"]')) || (main = body.at_xpath('//main'))
|
196
|
-
main.children
|
197
|
-
elsif (article = body.at_xpath('//article'))
|
198
|
-
article.children
|
199
|
-
else
|
200
|
-
body_elem = body.clone
|
201
|
-
%w{header nav masthead footer}.each do |name|
|
202
|
-
if (elem = body_elem.at_xpath("//div[@id=\"#{name}\"]")) || (elem = body_elem.at_xpath("//#{name}"))
|
203
|
-
elem.remove
|
204
|
-
end
|
205
|
-
end
|
206
|
-
body_elem.children
|
207
|
-
end
|
208
|
-
else
|
209
|
-
warn "Warning: Resource #{path} (#{self.class}) has no content"
|
210
|
-
nil
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|
215
|
-
|
216
|
-
end
|
217
|
-
|
218
|
-
end
|
data/lib/mill/version.rb
DELETED
data/test/test.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
$VERBOSE = false
|
2
|
-
|
3
|
-
require 'minitest/autorun'
|
4
|
-
require 'minitest/power_assert'
|
5
|
-
|
6
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
7
|
-
require 'mill'
|
8
|
-
|
9
|
-
module Mill
|
10
|
-
|
11
|
-
class Test < Minitest::Test
|
12
|
-
|
13
|
-
def setup
|
14
|
-
@site = Site.new(
|
15
|
-
input_dir: 'test/content',
|
16
|
-
output_dir: 'test/output',
|
17
|
-
site_title: 'Test',
|
18
|
-
site_uri: 'http://test.test',
|
19
|
-
html_version: :html5,
|
20
|
-
)
|
21
|
-
@site.make
|
22
|
-
# ;;@site.print_tree
|
23
|
-
# ;;@site.list
|
24
|
-
# ;;binding.pry
|
25
|
-
@root = @site.find_resource('/') or raise
|
26
|
-
@a = @site.find_resource('/a') or raise
|
27
|
-
@b = @site.find_resource('/b') or raise
|
28
|
-
@ba = @site.find_resource('/b/ba') or raise
|
29
|
-
@bb = @site.find_resource('/b/bb') or raise
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_has_index
|
33
|
-
assert { @root }
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_children
|
37
|
-
assert { @root.children == [@a, @b] }
|
38
|
-
assert { @a.children.empty? }
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_parent
|
42
|
-
assert { @a.parent == @root }
|
43
|
-
assert { @b.parent == @root }
|
44
|
-
assert { @ba.parent == @b }
|
45
|
-
assert { @bb.parent == @b }
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_siblings
|
49
|
-
assert { @root.siblings.empty? }
|
50
|
-
assert { @a.siblings == [@b] }
|
51
|
-
assert { @ba.siblings == [@bb] }
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|