henshin 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -1
- data/README.markdown +9 -46
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/henshin +11 -12
- data/henshin.gemspec +20 -27
- data/lib/henshin.rb +21 -98
- data/lib/henshin/archive.rb +87 -115
- data/{bin → lib/henshin/exec}/files.rb +0 -0
- data/lib/henshin/ext.rb +35 -58
- data/lib/henshin/gen.rb +83 -68
- data/lib/henshin/labels.rb +144 -0
- data/lib/henshin/plugin.rb +70 -33
- data/lib/henshin/plugins/highlight.rb +19 -26
- data/lib/henshin/plugins/liquid.rb +50 -52
- data/lib/henshin/plugins/maruku.rb +14 -16
- data/lib/henshin/plugins/sass.rb +19 -23
- data/lib/henshin/plugins/textile.rb +14 -16
- data/lib/henshin/post.rb +67 -120
- data/lib/henshin/site.rb +154 -131
- data/lib/henshin/static.rb +10 -8
- data/test/helper.rb +20 -8
- data/test/site/css/{includes/reset.sass → _reset.sass} +0 -0
- data/test/site/css/screen.sass +1 -1
- data/test/site/index.html +2 -3
- data/test/site/layouts/archive_date.html +5 -4
- data/test/site/layouts/archive_month.html +10 -5
- data/test/site/layouts/archive_year.html +13 -6
- data/test/site/layouts/category_page.html +7 -7
- data/test/site/layouts/main.html +3 -4
- data/test/site/layouts/post.html +1 -1
- data/test/site/layouts/tag_index.html +3 -2
- data/test/site/layouts/tag_page.html +6 -6
- data/test/site/options.yaml +2 -2
- data/test/site/plugins/test.rb +1 -3
- data/test/site/posts/Testing-Stuff.markdown +1 -1
- data/test/site/posts/Textile-Test.textile +1 -1
- data/test/site/posts/lorem-ipsum.markdown +1 -1
- data/test/site/posts/same-date.markdown +1 -1
- data/test/{test_henshin.rb → suite.rb} +0 -1
- data/test/test_gen.rb +98 -0
- data/test/test_options.rb +34 -19
- data/test/test_post.rb +67 -0
- data/test/test_site.rb +159 -46
- data/test/test_static.rb +13 -0
- metadata +53 -32
- data/lib/henshin/categories.rb +0 -29
- data/lib/henshin/tags.rb +0 -28
- data/test/test_archives.rb +0 -27
- data/test/test_categories.rb +0 -0
- data/test/test_gens.rb +0 -54
- data/test/test_layouts.rb +0 -14
- data/test/test_posts.rb +0 -75
- data/test/test_statics.rb +0 -0
- data/test/test_tags.rb +0 -0
- data/test/text_exts.rb +0 -0
File without changes
|
data/lib/henshin/ext.rb
CHANGED
@@ -1,53 +1,8 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
module Enumerable
|
4
|
-
|
5
|
-
# Like an each loop but runs each task in parallel
|
6
|
-
# Which will probably be very useful for writing and rendering
|
7
|
-
# from http://t-a-w.blogspot.com/2010/05/very-simple-parallelization-with-ruby.html
|
8
|
-
def each_parallel( n=10 )
|
9
|
-
todo = Queue.new
|
10
|
-
ts = (1..n).map do
|
11
|
-
Thread.new do
|
12
|
-
while x = todo.deq
|
13
|
-
Exception.ignoring_exceptions { yield(x[0]) }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
each {|x| todo << [x] }
|
18
|
-
n.times { todo << nil }
|
19
|
-
ts.each {|t| t.join }
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def Exception.ignoring_exceptions
|
25
|
-
begin
|
26
|
-
yield
|
27
|
-
rescue Exception => e
|
28
|
-
STDERR.puts e
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
class Hash
|
34
|
-
|
35
|
-
# stripped straight out of rails
|
36
|
-
# converts string keys to symbol keys
|
37
|
-
# from http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Keys.html
|
38
|
-
def to_options
|
39
|
-
inject({}) do |options, (key, value)|
|
40
|
-
options[(key.to_sym rescue key) || key] = value
|
41
|
-
options
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
1
|
class String
|
49
2
|
|
50
3
|
# Turns the string to a slug
|
4
|
+
#
|
5
|
+
# @return [String] the created slug
|
51
6
|
def slugify
|
52
7
|
slug = self.clone
|
53
8
|
slug.gsub!(/[']+/, '')
|
@@ -58,21 +13,43 @@ class String
|
|
58
13
|
slug
|
59
14
|
end
|
60
15
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
16
|
+
# Converts the String to a Pathname object
|
17
|
+
#
|
18
|
+
# @return [Pathname]
|
19
|
+
def to_p
|
20
|
+
Pathname.new(self)
|
65
21
|
end
|
66
22
|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
23
|
+
# Checks whether it is a valid number in a string, or not
|
24
|
+
# @see http://www.railsforum.com/viewtopic.php?id=19081
|
25
|
+
def numeric?
|
26
|
+
true if Float(self) rescue false
|
71
27
|
end
|
72
28
|
|
73
|
-
|
74
|
-
|
75
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
class Hash
|
32
|
+
|
33
|
+
# Converts string hash keys to symbol keys
|
34
|
+
#
|
35
|
+
# @see http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Keys.html
|
36
|
+
# stolen from rails
|
37
|
+
def to_options
|
38
|
+
inject({}) do |options, (key, value)|
|
39
|
+
options[(key.to_sym rescue key) || key] = value
|
40
|
+
options
|
41
|
+
end
|
76
42
|
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class Pathname
|
77
47
|
|
48
|
+
# Gets just the extension of the pathname
|
49
|
+
#
|
50
|
+
# @return [String] the extension of the path, without the '.'
|
51
|
+
def extension
|
52
|
+
self.extname[1..-1]
|
53
|
+
end
|
54
|
+
|
78
55
|
end
|
data/lib/henshin/gen.rb
CHANGED
@@ -1,133 +1,148 @@
|
|
1
1
|
module Henshin
|
2
2
|
|
3
|
-
# This is the main class for
|
3
|
+
# This is the main class for files which need to be rendered with plugins
|
4
4
|
class Gen
|
5
5
|
|
6
|
-
attr_accessor :path, :
|
7
|
-
attr_accessor :site, :config, :renderer, :data, :output
|
6
|
+
attr_accessor :path, :data, :content, :site, :to_inject, :generators
|
8
7
|
|
9
|
-
|
8
|
+
# Creates a new instance of Gen
|
9
|
+
#
|
10
|
+
# @param [Pathname] path to the file
|
11
|
+
# @param [Site] the site the gen belongs to
|
12
|
+
# @param [Hash] an optional payload to add when rendered
|
13
|
+
def initialize(path, site, to_inject=nil)
|
10
14
|
@path = path
|
11
15
|
@site = site
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
16
|
+
@data = {}
|
17
|
+
@content = ''
|
18
|
+
@to_inject = to_inject
|
19
|
+
@generators = []
|
20
|
+
|
21
|
+
@data['input'] = @path.extension
|
15
22
|
end
|
16
23
|
|
17
24
|
|
18
25
|
##
|
19
|
-
#
|
20
|
-
def
|
21
|
-
self.
|
26
|
+
# Reads the file if it exists, if not gets generators and layout, then cleans up @data
|
27
|
+
def read
|
28
|
+
self.read_file if @path.exist?
|
29
|
+
self.get_generators
|
30
|
+
self.get_layout
|
31
|
+
|
32
|
+
# tidy up data
|
33
|
+
@data['output'] ||= @data['input']
|
34
|
+
self
|
22
35
|
end
|
23
36
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
37
|
+
# Opens the file and reads the yaml frontmatter if any exists, and
|
38
|
+
# also gets the contents of the file.
|
39
|
+
def read_file
|
40
|
+
file = @path.read
|
41
|
+
|
28
42
|
if file =~ /^(---\s*\n.*?\n?^---\s*$\n?)/m
|
29
|
-
override = YAML.load_file(@path)
|
30
|
-
|
43
|
+
override = YAML.load_file(@path)
|
44
|
+
@data = @data.merge(override)
|
31
45
|
@content = file[$1.size..-1]
|
32
46
|
else
|
33
47
|
@content = file
|
34
|
-
end
|
48
|
+
end
|
35
49
|
end
|
36
50
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
# Finds the correct plugins to render this gen and sets output
|
52
|
+
def get_generators
|
53
|
+
@site.plugins[:generators].each do |k, v|
|
54
|
+
if k == @data['input'] || k == '*'
|
55
|
+
@generators << v
|
56
|
+
@data['output'] ||= v.extensions[:output]
|
57
|
+
@data['ignore_layout'] ||= (v.config['ignore_layouts'] ? true : false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@generators.sort!
|
43
61
|
end
|
44
62
|
|
63
|
+
# Gets the correct layout for the gen, or the default if none exists.
|
64
|
+
# It gets the default layout from options.yaml or looks for one called
|
65
|
+
# 'main' or 'default'.
|
66
|
+
def get_layout
|
67
|
+
if @data['layout']
|
68
|
+
@data['layout'] = site.layouts[ @data['layout'] ]
|
69
|
+
else
|
70
|
+
# get default layout
|
71
|
+
@data['layout'] = site.layouts[(site.config['layout'] || 'main' || 'default')]
|
72
|
+
end
|
73
|
+
end
|
45
74
|
|
46
75
|
##
|
47
|
-
# Renders the files content
|
76
|
+
# Renders the files content using the generators from #get_generators and all layout parsers.
|
77
|
+
# Passed through layout parser twice so that markup in the gen is processed.
|
48
78
|
def render
|
49
|
-
|
50
|
-
plugins = []
|
51
|
-
|
52
|
-
config[:plugins][:generators].each do |k, v|
|
53
|
-
if k == @extension || k == '*'
|
54
|
-
plugins << v
|
55
|
-
end
|
56
|
-
end
|
57
|
-
plugins.sort!
|
58
|
-
|
59
|
-
plugins.each do |plugin|
|
79
|
+
@generators.each do |plugin|
|
60
80
|
@content = plugin.generate(@content)
|
61
|
-
@output = plugin.extensions[:output] if plugin.extensions[:output]
|
62
|
-
ignore_layout = true if plugin.config[:ignore_layouts]
|
63
81
|
end
|
64
|
-
|
65
|
-
@
|
66
|
-
|
67
|
-
|
68
|
-
@content = plugin.generate(
|
69
|
-
@content = plugin.generate( @content, self.payload )
|
82
|
+
|
83
|
+
unless @data['ignore_layout'] || @data['layout'].nil?
|
84
|
+
@site.plugins[:layoutors].each do |plugin|
|
85
|
+
@content = plugin.generate(@data['layout'], self.payload)
|
86
|
+
@content = plugin.generate(@content, self.payload)
|
70
87
|
end
|
71
88
|
end
|
72
89
|
|
73
90
|
end
|
74
91
|
|
75
|
-
# Creates the data to be sent to the layout engine. Adds optional data if available
|
92
|
+
# Creates the data to be sent to the layout engine. Adds optional data if available.
|
76
93
|
#
|
77
94
|
# @return [Hash] the payload for the layout engine
|
78
95
|
def payload
|
79
96
|
hash = {
|
80
97
|
'yield' => @content,
|
81
98
|
'gen' => self.to_hash,
|
82
|
-
'site' => @site.payload['site']
|
99
|
+
'site' => @site.payload['site']
|
83
100
|
}
|
84
|
-
hash[ @
|
85
|
-
|
101
|
+
hash[ @to_inject[:name] ] = @to_inject[:payload] if @to_inject
|
86
102
|
hash
|
87
103
|
end
|
88
104
|
|
89
|
-
# Turns all of the
|
105
|
+
# Turns all of the gens data into a hash.
|
90
106
|
#
|
91
107
|
# @return [Hash]
|
92
108
|
def to_hash
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
'content' => @content
|
98
|
-
}
|
109
|
+
@data['content'] = @content
|
110
|
+
@data['url'] = self.url
|
111
|
+
@data['permalink'] = self.permalink
|
112
|
+
@data
|
99
113
|
end
|
100
114
|
|
101
115
|
|
102
116
|
##
|
103
117
|
# Writes the file to the correct place
|
104
118
|
def write
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
write_path.gsub!(".#{@extension}", ".#{@output}") if @output
|
109
|
-
|
110
|
-
FileUtils.mkdir_p File.join( write_path.directory )
|
111
|
-
file = File.new( File.join( write_path ), "w" )
|
112
|
-
file.puts( @content )
|
119
|
+
FileUtils.mkdir_p(self.write_path.dirname)
|
120
|
+
file = File.new(self.write_path, "w")
|
121
|
+
file.puts(@content)
|
113
122
|
end
|
114
123
|
|
115
|
-
#
|
124
|
+
# @return [String] the permalink of the gen
|
116
125
|
def permalink
|
117
|
-
@path
|
126
|
+
rel = @path.relative_path_from(@site.root).to_s
|
127
|
+
rel.gsub!(".#{@data['input']}", ".#{@data['output']}")
|
128
|
+
File.join(@site.base, rel)
|
118
129
|
end
|
119
130
|
|
120
|
-
#
|
131
|
+
# @return [String] the pretty url for the gen
|
121
132
|
def url
|
122
|
-
if config[
|
123
|
-
self.permalink
|
133
|
+
if @site.config['permalink'].include?("/index.html") && @data['output'] == 'html'
|
134
|
+
self.permalink.to_p.dirname.to_s
|
124
135
|
else
|
125
136
|
self.permalink
|
126
137
|
end
|
127
138
|
end
|
128
139
|
|
140
|
+
# @return [Pathname] path to write the file to
|
141
|
+
def write_path
|
142
|
+
@site.target + self.permalink[1..-1]
|
143
|
+
end
|
129
144
|
|
130
|
-
#
|
145
|
+
# Sorts gens based on permalink only
|
131
146
|
def <=>( other )
|
132
147
|
self.permalink <=> other.permalink
|
133
148
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# This is basically a front for tags and categories, because they are so similar
|
4
|
+
# it makes sense to condense them into one class!
|
5
|
+
#
|
6
|
+
class Labels < Array
|
7
|
+
|
8
|
+
attr_accessor :base, :site
|
9
|
+
|
10
|
+
# Creates a new instance of labels
|
11
|
+
#
|
12
|
+
# @param [String] base the base part of the urls, eg. category, tag
|
13
|
+
# @param [Site] site that the labels belong to
|
14
|
+
def initialize(base, site)
|
15
|
+
@base = base
|
16
|
+
@site = site
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds the given post to the correct category object in the array
|
20
|
+
# or creates the category and adds the post to that
|
21
|
+
#
|
22
|
+
# @param [Post] post to be added
|
23
|
+
# @param [String, Array] k label(s) to be added to
|
24
|
+
#
|
25
|
+
# @todo Make it a bit more abstract, actually hard coding stuff in will
|
26
|
+
# lead to problems!
|
27
|
+
def <<(post)
|
28
|
+
k = nil
|
29
|
+
if base == 'tag'
|
30
|
+
k = post.data['tags']
|
31
|
+
elsif base == 'category'
|
32
|
+
k = [post.data['category']]
|
33
|
+
end
|
34
|
+
|
35
|
+
k.each do |j|
|
36
|
+
unless self.map{|i| i.name}.include?(j)
|
37
|
+
super Henshin::Label.new(j, @base, @site)
|
38
|
+
end
|
39
|
+
i = self.find_index {|i| i.name == j}
|
40
|
+
self[i].posts << post
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the labels to a hash for use in a layout parser
|
45
|
+
def to_hash
|
46
|
+
r = []
|
47
|
+
self.each do |i|
|
48
|
+
r << i.to_hash
|
49
|
+
end
|
50
|
+
r
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] permalink for label index
|
54
|
+
def permalink
|
55
|
+
File.join(@site.base, @base, "index.html")
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] base url for label
|
59
|
+
def url
|
60
|
+
File.join(@site.base, @base)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Need a fake path where the file would have been so as to
|
64
|
+
# trick the gen into constructing the correct paths
|
65
|
+
#
|
66
|
+
# @return [Pathname] the path for the gen
|
67
|
+
def fake_write_path
|
68
|
+
@site.root + self.permalink[1..-1]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Writes the category index, then writes the individual
|
72
|
+
# category pages
|
73
|
+
def write
|
74
|
+
if @site.layouts["#{@base}_index"]
|
75
|
+
page = Gen.new(self.fake_write_path, @site)
|
76
|
+
page.read
|
77
|
+
page.data['layout'] = @site.layouts["#{@base}_index"]
|
78
|
+
|
79
|
+
page.render
|
80
|
+
page.write
|
81
|
+
end
|
82
|
+
if @site.layouts["#{@base}_page"]
|
83
|
+
self.each {|label| label.write }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Label
|
90
|
+
attr_accessor :name, :posts, :site
|
91
|
+
|
92
|
+
# Creates a new instance of label
|
93
|
+
#
|
94
|
+
# @param [String] name of the label
|
95
|
+
# @param [String] base of the url for the label (see Labels#initialize)
|
96
|
+
# @param [Site] site that the label belongs to
|
97
|
+
def initialize(name, base, site)
|
98
|
+
@name = name
|
99
|
+
@base = base
|
100
|
+
@site = site
|
101
|
+
@posts = []
|
102
|
+
end
|
103
|
+
|
104
|
+
# Converts the label to a hash
|
105
|
+
def to_hash
|
106
|
+
hash = {
|
107
|
+
'name' => @name,
|
108
|
+
'posts' => @posts.sort.collect {|i| i.to_hash},
|
109
|
+
'url' => self.url
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [String] permalink for the label
|
114
|
+
def permalink
|
115
|
+
File.join(@site.base, @base, "#{@name.slugify}/index.html")
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [String] url for the label
|
119
|
+
def url
|
120
|
+
File.join(@site.base, @base, "#{@name.slugify}")
|
121
|
+
end
|
122
|
+
|
123
|
+
# @see Labels#fake_write_path
|
124
|
+
def fake_write_path
|
125
|
+
@site.root + self.permalink[1..-1]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Writes the label page
|
129
|
+
def write
|
130
|
+
payload = {:name => @base, :payload => self.to_hash}
|
131
|
+
page = Gen.new(self.fake_write_path, @site, payload)
|
132
|
+
page.read
|
133
|
+
page.data['layout'] = @site.layouts["#{@base}_page"]
|
134
|
+
|
135
|
+
page.render
|
136
|
+
page.write
|
137
|
+
end
|
138
|
+
|
139
|
+
def inspect
|
140
|
+
"#<Label:#{@base}/#{@name}>"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|