henshin 0.3.0 → 0.4.0
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.
- 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
|