mill 0.1 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
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,6 +1,6 @@
1
1
  # see http://www.sitemaps.org/protocol.php
2
2
 
3
- class Mill
3
+ module Mill
4
4
 
5
5
  class Resource
6
6
 
@@ -8,25 +8,21 @@ class Mill
8
8
 
9
9
  include HTMLHelpers
10
10
 
11
- def self.type
12
- :feed
13
- end
14
-
15
- def load
16
- resources = @mill.public_resources.sort_by(&:date)
11
+ def build
12
+ resources = @site.feed_resources
17
13
  builder = Nokogiri::XML::Builder.new do |xml|
18
14
  xml.feed(xmlns: 'http://www.w3.org/2005/Atom') do
19
- xml.id(@mill.tag_uri)
20
- xml.generator(*@mill.feed_generator)
21
- xml.title(@mill.site_title)
22
- xml.link(rel: 'alternate', type: 'text/html', href: @mill.home_resource.uri)
15
+ xml.id(@site.tag_uri)
16
+ # xml.generator(*@site.feed_generator)
17
+ xml.title(@site.site_title) if @site.site_title
18
+ xml.link(rel: 'alternate', type: 'text/html', href: @site.home_resource.uri) if @site.home_resource
23
19
  xml.link(rel: 'self', type: 'application/atom+xml', href: uri)
24
20
  xml.author do
25
- xml.name(@mill.feed_author_name)
26
- xml.uri(@mill.feed_author_uri)
27
- xml.email(@mill.feed_author_email)
21
+ xml.name(@site.feed_author_name) if @site.feed_author_name
22
+ xml.uri(@site.feed_author_uri) if @site.feed_author_uri
23
+ xml.email(@site.feed_author_email) if @site.feed_author_email
28
24
  end
29
- xml.updated(resources.last.date.iso8601)
25
+ xml.updated(resources.last.date.iso8601) unless resources.empty?
30
26
  resources.each do |resource|
31
27
  xml.entry do
32
28
  xml.title(resource.title) if resource.title
@@ -34,13 +30,8 @@ class Mill
34
30
  xml.id(resource.tag_uri)
35
31
  xml.updated(resource.date.iso8601)
36
32
  xml.published(resource.date.iso8601)
37
- if (resource.respond_to?(:feed_summary))
38
- type, data = resource.feed_summary
39
- xml.summary(type: type) { xml.cdata(data) } if type && data
40
- end
41
- if (resource.respond_to?(:feed_content))
42
- type, data = resource.feed_content
43
- xml.content(type: type) { xml.cdata(data) }
33
+ if (html = resource.feed_content)
34
+ xml.content(type: 'html') { xml.cdata(html.to_html) }
44
35
  end
45
36
  end
46
37
  end
@@ -0,0 +1,24 @@
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 load
16
+ @content = "google-site-verification: #{@key}.html\n"
17
+ super
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -1,4 +1,4 @@
1
- class Mill
1
+ module Mill
2
2
 
3
3
  class Resource
4
4
 
@@ -6,13 +6,20 @@ class Mill
6
6
 
7
7
  include HTMLHelpers
8
8
 
9
+ FileTypes = %w{
10
+ image/gif
11
+ image/jpeg
12
+ image/png
13
+ image/tiff
14
+ image/vnd.microsoft.icon
15
+ image/x-icon
16
+ image/svg
17
+ image/svg+xml
18
+ }
19
+
9
20
  attr_accessor :width
10
21
  attr_accessor :height
11
22
 
12
- def self.type
13
- :image
14
- end
15
-
16
23
  def load
17
24
  info = ImageSize.path(@input_file.to_s)
18
25
  @width, @height = *info.size
@@ -0,0 +1,29 @@
1
+ module Mill
2
+
3
+ class Resource
4
+
5
+ class Other < Resource
6
+
7
+ FileTypes = %w{
8
+ application/pdf
9
+
10
+ application/x-shockwave-flash
11
+
12
+ application/font-sfnt
13
+ application/x-font-opentype
14
+ application/x-font-otf
15
+ application/x-font-truetype
16
+ application/x-font-ttf
17
+
18
+ application/rtf
19
+
20
+ application/javascript
21
+ application/x-javascript
22
+ text/javascript
23
+ }
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -1,4 +1,4 @@
1
- class Mill
1
+ module Mill
2
2
 
3
3
  class Resource
4
4
 
@@ -7,16 +7,10 @@ class Mill
7
7
  attr_accessor :redirect_uri
8
8
  attr_accessor :redirect_code
9
9
 
10
- def self.type
11
- :redirect
12
- end
13
-
14
- def initialize(params={})
15
- super(
16
- {
17
- redirect_code: 303,
18
- }.merge(params)
19
- )
10
+ def initialize(redirect_uri:, redirect_code: 303, **args)
11
+ @redirect_uri = redirect_uri
12
+ @redirect_code = redirect_code
13
+ super(**args)
20
14
  end
21
15
 
22
16
  def load
@@ -24,7 +18,7 @@ class Mill
24
18
  super
25
19
  end
26
20
 
27
- def build
21
+ def save
28
22
  @output_file = @output_file.add_extension('.redirect')
29
23
  super
30
24
  end
@@ -33,4 +27,4 @@ class Mill
33
27
 
34
28
  end
35
29
 
36
- end
30
+ end
@@ -1,19 +1,16 @@
1
1
  # see http://www.robotstxt.org/robotstxt.html
2
2
 
3
- class Mill
3
+ module Mill
4
4
 
5
5
  class Resource
6
6
 
7
7
  class Robots < Resource
8
8
 
9
- def self.type
10
- :robots
11
- end
12
-
13
- def load
14
- info = {
15
- 'Sitemap' => @mill.sitemap_resource.absolute_uri,
16
- }
9
+ def build
10
+ info = {}
11
+ info['User-Agent'] = '*'
12
+ info['Disallow'] = '/' unless @site.allow_robots
13
+ info['Sitemap'] = @site.sitemap_resource.absolute_uri if @site.make_sitemap
17
14
  @content = info.map { |key, value| "#{key}: #{value}" }.join("\n")
18
15
  super
19
16
  end
@@ -1,21 +1,17 @@
1
1
  # see http://www.sitemaps.org/protocol.php
2
2
 
3
- class Mill
3
+ module Mill
4
4
 
5
5
  class Resource
6
6
 
7
7
  class Sitemap < Resource
8
8
 
9
- def self.type
10
- :sitemap
11
- end
12
-
13
- def load
9
+ def build
14
10
  builder = Nokogiri::XML::Builder.new do |xml|
15
11
  xml.urlset('xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
16
12
  'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
17
13
  'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd') do
18
- @mill.public_resources.each do |resource|
14
+ @site.public_resources.each do |resource|
19
15
  xml.url do
20
16
  xml.loc(resource.absolute_uri)
21
17
  xml.lastmod(resource.date.iso8601)
@@ -0,0 +1,27 @@
1
+ module Mill
2
+
3
+ class Resource
4
+
5
+ class Stylesheet < Resource
6
+
7
+ FileTypes = %w{
8
+ text/css
9
+ }
10
+
11
+ def load
12
+ super
13
+ unless @input_file.basename.to_s.end_with?('.min.css')
14
+ engine = Sass::Engine.for_file(@input_file.to_s, syntax: :scss, style: :compressed)
15
+ begin
16
+ @content = engine.render
17
+ rescue Sass::SyntaxError => e
18
+ raise "#{input_file}: error parsing CSS: #{e}"
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -1,4 +1,4 @@
1
- class Mill
1
+ module Mill
2
2
 
3
3
  class Resource
4
4
 
@@ -6,54 +6,79 @@ class Mill
6
6
 
7
7
  include HTMLHelpers
8
8
 
9
- attr_accessor :title
9
+ FileTypes = %w{
10
+ text/plain
11
+ text/html
12
+ }
10
13
 
11
- def self.type
12
- :text
14
+ attr_accessor :title
15
+ attr_accessor :summary
16
+ attr_accessor :author
17
+
18
+ def initialize(title: nil, summary: nil, author: nil, public: true, **args)
19
+ @title = title
20
+ @summary = summary
21
+ @author = author
22
+ super(public: public, **args)
13
23
  end
14
24
 
15
- def initialize(params={})
16
- super(
17
- {
18
- public: true,
19
- }.merge(params)
20
- )
25
+ def inspect
26
+ super + ", title: %p, summary: %p, author: %p" % [
27
+ @title,
28
+ @summary,
29
+ @author,
30
+ ]
21
31
  end
22
32
 
23
33
  def load
34
+ super
24
35
  if @input_file
25
36
  @content = @input_file.read
26
- markup_class = case @input_file.extname
37
+ mode = case @input_file.extname.downcase
27
38
  when '.md', '.mdown', '.markdown'
28
- Kramdown::Document
39
+ :markdown
29
40
  when '.textile'
30
- RedCloth
41
+ :textile
31
42
  when '.txt'
32
- PreText
43
+ :pre
44
+ when '.htm', '.html'
45
+ :html
33
46
  else
34
- nil
47
+ raise "Unknown text type: #{@input_file}"
35
48
  end
36
- if markup_class
49
+ if mode != :html
37
50
  parse_text_header
38
- raise "#{uri}: Content is empty" unless @content
39
- @content = markup_class.new(@content).to_html
51
+ @content = (@content || '').to_html(mode: mode, multiline: true)
40
52
  @output_file = @output_file.replace_extension('.html')
41
53
  end
42
54
  begin
43
55
  @content = parse_html(@content)
44
- rescue HTMLError => e
45
- raise "failed to parse #{@input_file}: #{e}"
56
+ rescue Error => e
57
+ raise e, "#{@input_file}: #{e}"
46
58
  end
47
59
  parse_html_header
48
60
  end
49
- add_image_sizes
50
- convert_relative_links
61
+ end
62
+
63
+ def build
64
+ replace_elements
51
65
  super
52
66
  end
53
67
 
68
+ def replace_elements
69
+ replace_pages_element
70
+ remove_comments
71
+ add_image_sizes
72
+ # shorten_links
73
+ end
74
+
54
75
  def parse_html_header
55
- if (title_elem = @content.at_xpath('/html/head/title'))
56
- @title = title_elem.text
76
+ unless @title
77
+ if (title_elem = @content.at_xpath('/html/head/title'))
78
+ @title = title_elem.text
79
+ else
80
+ @title = uri.to_s
81
+ end
57
82
  end
58
83
  @content.xpath('/html/head/meta[@name]').each do |meta|
59
84
  send("#{meta['name']}=", meta['content'])
@@ -61,7 +86,7 @@ class Mill
61
86
  end
62
87
 
63
88
  def parse_text_header
64
- if @content =~ /^\w+:\s+/
89
+ if @content.split(/\n/, 2).first =~ /^\w+:\s+/
65
90
  header, @content = @content.split(/\n\n/, 2)
66
91
  header.split(/\n/).map do |line|
67
92
  key, value = line.strip.split(/:\s+/, 2)
@@ -72,82 +97,137 @@ class Mill
72
97
  end
73
98
 
74
99
  def final_content
75
- html_document do |doc|
100
+ html_document(@site.html_version) do |doc|
76
101
  doc.html(lang: 'en') do |html|
77
- html.head do
78
- html << head.to_html
102
+ html << head.to_html
103
+ html << body.to_html
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 }
79
114
  end
80
- html.body do
81
- html << body.to_html
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
82
120
  end
83
121
  end
84
122
  end
85
123
  end
86
124
 
87
- def head
88
- @content.at_xpath('/html/head').children
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
89
134
  end
90
135
 
91
- def body
92
- @content.at_xpath('/html/body').children
136
+ def content_head
137
+ @content && @content.at_xpath('/html/head')
93
138
  end
94
139
 
95
- def verify
96
- tidy_html(@output_file.read) do |error_str|
97
- warn "#{uri}: #{error_str}"
98
- end
140
+ def content_body
141
+ @content && @content.at_xpath('/html/body')
99
142
  end
100
143
 
101
144
  def add_external_link_targets
102
145
  @content.xpath('//a').each do |a|
103
146
  if a['href'] && a['href'] =~ /^\w+:/
104
147
  a['target'] = '_blank'
148
+ a['rel'] = 'noopener noreferrer'
105
149
  end
106
150
  end
107
151
  end
108
152
 
153
+ def remove_comments
154
+ @content.xpath('//comment()').each do |comment|
155
+ comment.remove
156
+ end
157
+ end
158
+
109
159
  def add_image_sizes
110
160
  @content.xpath('//img').each do |img|
111
161
  # skip elements that already have width/height defined
112
162
  next if img[:width] || img[:height]
113
163
  img_link = Addressable::URI.parse(img['src'])
114
- raise "no link in <img> element: #{img.to_s}" if img_link.nil? || img_link.empty?
164
+ raise Error, "No link in <img> element: #{img.to_s}" if img_link.nil? || img_link.empty?
115
165
  next if img_link.host
116
166
  img_uri = uri + img_link
117
- img_resource = @mill.find_resource(img_uri) or raise "Can't find image for #{img_uri}"
167
+ img_resource = @site.find_resource(img_uri) or raise Error, "Can't find image for #{img_uri}"
118
168
  img[:width], img[:height] = img_resource.width, img_resource.height
119
169
  end
120
170
  end
121
171
 
122
- def convert_relative_links
123
- @mill.link_elem_attrs.each do |xpath|
124
- convert_relative_link(xpath)
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 "[#{uri}] shortened link #{elem.name}/@#{attribute.name}: #{link_uri} => #{attribute.value}"
183
+ end
125
184
  end
126
185
  end
127
186
 
128
- def convert_relative_link(elem_attr)
129
- @content.xpath("//#{elem_attr}").each do |attribute|
130
- elem = attribute.parent
131
- link_uri = Addressable::URI.parse(attribute.value) or raise "Can't parse #{attribute.value.inspect} from #{xpath.inspect}"
132
- if !link_uri.path.empty? && link_uri.path[0] != '/'
133
- attribute.value = uri + link_uri
134
- # ;;warn "[#{uri}] absolutized #{elem.name}/@#{attribute.name}: #{link_uri} => #{attribute.value}"
187
+ def replace_pages_element
188
+ replace_element(@content, '//pages') do |elem|
189
+ html_fragment do |html|
190
+ html.dl do
191
+ find_sibling_resources(Resource::Text).each do |page|
192
+ html.dt do
193
+ html.a(href: page.uri) { html << page.title.to_html }
194
+ end
195
+ html.dd do
196
+ if (summary = page.summary)
197
+ html << summary.to_html
198
+ end
199
+ end
200
+ end
201
+ end
135
202
  end
136
203
  end
137
204
  end
138
205
 
139
- def feed_summary
140
- ;;raise "#{uri} has no content" unless @content
141
- if (p = @content.at_xpath('/html/body/p[1]'))
142
- ['html', p.to_html]
143
- else
144
- nil
145
- end
206
+ def summary
207
+ @summary || ((p = feed_content.at_xpath('//p')) && p.children)
146
208
  end
147
209
 
148
210
  def feed_content
149
- body = @content.at_xpath('/html/body') or raise "#{uri} has no content"
150
- ['html', body.children.to_html]
211
+ if (body = content_body)
212
+ # If we have a main element (<div class="main"> or <main>), use that.
213
+ # Otherwise, use the body, but delete header/footer/nav divs or elements.
214
+ if (main = body.at_xpath('//div[@id="main"]')) || (main = body.at_xpath('//main'))
215
+ main.children
216
+ elsif (article = body.at_xpath('//article'))
217
+ article.children
218
+ else
219
+ body_elem = body.clone
220
+ %w{header nav masthead footer}.each do |name|
221
+ if (elem = body_elem.at_xpath("//div[@id=\"#{name}\"]")) || (elem = body_elem.at_xpath("//#{name}"))
222
+ elem.remove
223
+ end
224
+ end
225
+ body_elem.children
226
+ end
227
+ else
228
+ warn "Warning: Resource #{uri} (#{self.class}) has no content"
229
+ nil
230
+ end
151
231
  end
152
232
 
153
233
  end