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
@@ -0,0 +1,140 @@
|
|
1
|
+
module Mill
|
2
|
+
|
3
|
+
class Resource
|
4
|
+
|
5
|
+
class Page < Resource
|
6
|
+
|
7
|
+
FileTypes = %w{
|
8
|
+
text/html
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_accessor :title
|
12
|
+
attr_reader :document
|
13
|
+
|
14
|
+
def initialize(params={})
|
15
|
+
super({ primary: true }.merge(params))
|
16
|
+
@path.sub!(%r{\.\w+$}, '')
|
17
|
+
@path.sub!(%r{/index$}, '/')
|
18
|
+
@uri = Addressable::URI.encode(@path, Addressable::URI)
|
19
|
+
end
|
20
|
+
|
21
|
+
def printable
|
22
|
+
super + [
|
23
|
+
:title,
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
def output_file
|
28
|
+
if (file = super)
|
29
|
+
file /= 'index' if @path.end_with?('/')
|
30
|
+
file.add_extension('.html')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def load
|
35
|
+
@document = case @input
|
36
|
+
when Path
|
37
|
+
Simple::Builder.parse_html_document(@input.read)
|
38
|
+
when String
|
39
|
+
Simple::Builder.parse_html_document(@input)
|
40
|
+
when Nokogiri::HTML4::Document
|
41
|
+
@input
|
42
|
+
else
|
43
|
+
raise Error, "Unknown HTML input: #{@input.class}"
|
44
|
+
end
|
45
|
+
@title ||= @document.at_xpath('//h1')&.text || Simple::Builder.find_title_element(@document)&.text
|
46
|
+
set(Simple::Builder.find_meta_info(@document))
|
47
|
+
end
|
48
|
+
|
49
|
+
def build
|
50
|
+
raise Error, "No document defined" unless @document
|
51
|
+
builder = case @site.html_version
|
52
|
+
when :html4
|
53
|
+
:build_html4_document
|
54
|
+
when :html5, nil
|
55
|
+
:build_html5_document
|
56
|
+
else
|
57
|
+
raise "Unknown HTML version: #{@site.html_version.inspect}"
|
58
|
+
end
|
59
|
+
@output = Simple::Builder.send(builder) do |html|
|
60
|
+
html.html(lang: 'en') do
|
61
|
+
html.head do
|
62
|
+
build_head(html)
|
63
|
+
end
|
64
|
+
html.body do
|
65
|
+
build_body(html)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end.to_html
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_head(html)
|
72
|
+
title = @title || Simple::Builder.find_title_element(@document)&.text
|
73
|
+
html.title { html << title.to_html } if title
|
74
|
+
if (head = document_head&.children)
|
75
|
+
head.reject { |e| e.text? || e.name == 'title' }.each do |e|
|
76
|
+
html << e.to_html
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_body(html)
|
82
|
+
if (elem = document_body&.children)
|
83
|
+
html << elem.to_html
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def document_head
|
88
|
+
Simple::Builder.find_head_element(@document)
|
89
|
+
end
|
90
|
+
|
91
|
+
def document_body
|
92
|
+
Simple::Builder.find_body_element(@document)
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_image_sizes
|
96
|
+
@document.xpath('//img').each do |img|
|
97
|
+
# skip elements that already have width/height defined
|
98
|
+
next if img[:width] || img[:height]
|
99
|
+
img_link = Addressable::URI.parse(img['src'])
|
100
|
+
raise Error, "No link in <img> element: #{img.to_s}" if img_link.nil? || img_link.empty?
|
101
|
+
next if img_link.host
|
102
|
+
img_uri = @uri + img_link
|
103
|
+
img_resource = @site.find_resource(img_uri) or raise Error, "Can't find image for #{img_uri}"
|
104
|
+
img[:width], img[:height] = img_resource.width, img_resource.height
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def shorten_links
|
109
|
+
Simple::Builder.find_link_element_attributes(@document).each do |attribute|
|
110
|
+
link_uri = Addressable::URI.parse(attribute.value) or raise Error, "Can't parse attribute value: #{attribute.inspect}"
|
111
|
+
link_uri = @uri + link_uri
|
112
|
+
if link_uri.relative?
|
113
|
+
self_uri = @uri.normalize
|
114
|
+
self_uri.scheme = link_uri.scheme = @site.site_uri.scheme
|
115
|
+
attribute.value = self_uri.route_to(link_uri)
|
116
|
+
# ;;warn "[#{@path}] shortened link #{attribute.parent.name}/@#{attribute.name}: #{link_uri} => #{attribute.value}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def feed_content
|
122
|
+
document_body&.children
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_link(html)
|
126
|
+
html.a(href: @uri) { html << (@title || @path).to_html }
|
127
|
+
end
|
128
|
+
|
129
|
+
def links
|
130
|
+
raise "#{@path}: Can't get links for empty document: #{self}" unless @document
|
131
|
+
Simple::Builder.find_link_element_attributes(@document).map do |u|
|
132
|
+
(@uri + u).normalize
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -4,30 +4,31 @@ module Mill
|
|
4
4
|
|
5
5
|
class Redirect < Resource
|
6
6
|
|
7
|
-
|
7
|
+
attr_reader :redirect_uri
|
8
8
|
attr_accessor :redirect_code
|
9
9
|
|
10
|
-
def initialize(redirect_uri:, redirect_code:
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def initialize(redirect_uri:, redirect_code: nil, **params)
|
11
|
+
super(
|
12
|
+
{
|
13
|
+
redirect_uri: redirect_uri,
|
14
|
+
redirect_code: redirect_code || 303,
|
15
|
+
}.merge(params)
|
16
|
+
)
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
18
|
-
@redirect_uri.to_s,
|
19
|
-
@redirect_code,
|
20
|
-
]
|
19
|
+
def redirect_uri=(uri)
|
20
|
+
@redirect_uri = Addressable::URI.parse(uri)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
23
|
+
def printable
|
24
|
+
super + [
|
25
|
+
:redirect_uri,
|
26
|
+
:redirect_code,
|
27
|
+
]
|
26
28
|
end
|
27
29
|
|
28
|
-
def
|
29
|
-
@
|
30
|
-
super
|
30
|
+
def build
|
31
|
+
@output = "%s %d" % [@redirect_uri, @redirect_code]
|
31
32
|
end
|
32
33
|
|
33
34
|
end
|
@@ -9,10 +9,9 @@ module Mill
|
|
9
9
|
def build
|
10
10
|
info = {}
|
11
11
|
info['User-Agent'] = '*'
|
12
|
-
info['Disallow'] = '/' unless @site.allow_robots
|
13
|
-
info['Sitemap'] = @site.sitemap_resource.absolute_uri if @site.make_sitemap
|
14
|
-
@
|
15
|
-
super
|
12
|
+
info['Disallow'] = '/' unless @site.allow_robots?
|
13
|
+
info['Sitemap'] = @site.sitemap_resource.absolute_uri if @site.make_sitemap?
|
14
|
+
@output = info.map { |key, value| "#{key}: #{value}" }.join("\n")
|
16
15
|
end
|
17
16
|
|
18
17
|
end
|
@@ -7,11 +7,11 @@ module Mill
|
|
7
7
|
class Sitemap < Resource
|
8
8
|
|
9
9
|
def build
|
10
|
-
|
10
|
+
@output = Nokogiri::XML::Builder.new do |xml|
|
11
11
|
xml.urlset('xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
|
12
12
|
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
13
13
|
'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd') do
|
14
|
-
@site.
|
14
|
+
@site.sitemap_resources.each do |resource|
|
15
15
|
xml.url do
|
16
16
|
xml.loc(resource.absolute_uri)
|
17
17
|
xml.lastmod(resource.date.iso8601)
|
@@ -19,9 +19,7 @@ module Mill
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
-
end
|
23
|
-
@content = builder.doc
|
24
|
-
super
|
22
|
+
end.doc
|
25
23
|
end
|
26
24
|
|
27
25
|
end
|
@@ -9,12 +9,12 @@ module Mill
|
|
9
9
|
}
|
10
10
|
|
11
11
|
def load
|
12
|
-
|
13
|
-
unless @
|
12
|
+
raise Error, "Input must be file" unless @input.kind_of?(Path)
|
13
|
+
unless @input.basename.to_s.end_with?('.min.css')
|
14
14
|
begin
|
15
|
-
@
|
15
|
+
@output = SassC::Engine.new(@input.read, syntax: :scss, style: :compressed).render
|
16
16
|
rescue SassC::SyntaxError => e
|
17
|
-
raise "#{
|
17
|
+
raise "#{@input}: error parsing CSS: #{e}"
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mill
|
2
|
+
|
3
|
+
class Resource
|
4
|
+
|
5
|
+
class Textile < Markup
|
6
|
+
|
7
|
+
FileTypes = %w{
|
8
|
+
text/textile
|
9
|
+
}
|
10
|
+
|
11
|
+
if MIME::Types['text/textile'].empty?
|
12
|
+
type = MIME::Type.new(
|
13
|
+
'content-type' => 'text/textile',
|
14
|
+
'preferred-extension' => 'textile',
|
15
|
+
)
|
16
|
+
MIME::Types.add(type)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_text(text)
|
20
|
+
RedCloth.new((text || '').strip, [:no_span_caps]).to_html
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Mill
|
2
|
+
|
3
|
+
class Resources
|
4
|
+
|
5
|
+
include SetParams
|
6
|
+
|
7
|
+
def initialize(params={})
|
8
|
+
super
|
9
|
+
@dictionary = {}
|
10
|
+
@tree = Tree::TreeNode.new('')
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(resource)
|
14
|
+
@dictionary[resource.path] = resource
|
15
|
+
if resource.primary?
|
16
|
+
node = find_or_create_node(resource.path)
|
17
|
+
resource.node = node
|
18
|
+
node.content = resource
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
@dictionary.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](path)
|
27
|
+
path = path.path if path.kind_of?(Addressable::URI)
|
28
|
+
@dictionary[path] || @dictionary[path + '/']
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@dictionary.values.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def select(&block)
|
36
|
+
@dictionary.values.select(&block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def of_class(klass)
|
40
|
+
@dictionary.values.select { |r| r.kind_of?(klass) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(resource)
|
44
|
+
@dictionary.delete(resource.path) or raise Error, "No resource with path #{resource.path.inspect}"
|
45
|
+
if (node = find_node(resource.path))
|
46
|
+
node.delete
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_node(path)
|
51
|
+
node = @tree
|
52
|
+
path_components(path).each do |component|
|
53
|
+
node = node[component] or return nil
|
54
|
+
end
|
55
|
+
node
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_or_create_node(path)
|
59
|
+
node = @tree
|
60
|
+
path_components(path).each do |component|
|
61
|
+
node = node[component] || (node << Tree::TreeNode.new(component))
|
62
|
+
end
|
63
|
+
node
|
64
|
+
end
|
65
|
+
|
66
|
+
def path_components(path)
|
67
|
+
path.split('/').reject(&:empty?)
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_tree
|
71
|
+
print_node(@tree)
|
72
|
+
end
|
73
|
+
|
74
|
+
def print_node(node, level=0)
|
75
|
+
if node.is_root?
|
76
|
+
print '*'
|
77
|
+
else
|
78
|
+
print "\t" * level
|
79
|
+
end
|
80
|
+
print " #{node.name.inspect}"
|
81
|
+
print " <#{node.content&.path}>"
|
82
|
+
print " (#{node.children.length} children)" if node.has_children?
|
83
|
+
puts
|
84
|
+
node.children { |child| print_node(child, level + 1) }
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|