mill 0.1 → 0.3

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.
@@ -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