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.
Files changed (56) hide show
  1. data/.gitignore +5 -1
  2. data/README.markdown +9 -46
  3. data/Rakefile +2 -1
  4. data/VERSION +1 -1
  5. data/bin/henshin +11 -12
  6. data/henshin.gemspec +20 -27
  7. data/lib/henshin.rb +21 -98
  8. data/lib/henshin/archive.rb +87 -115
  9. data/{bin → lib/henshin/exec}/files.rb +0 -0
  10. data/lib/henshin/ext.rb +35 -58
  11. data/lib/henshin/gen.rb +83 -68
  12. data/lib/henshin/labels.rb +144 -0
  13. data/lib/henshin/plugin.rb +70 -33
  14. data/lib/henshin/plugins/highlight.rb +19 -26
  15. data/lib/henshin/plugins/liquid.rb +50 -52
  16. data/lib/henshin/plugins/maruku.rb +14 -16
  17. data/lib/henshin/plugins/sass.rb +19 -23
  18. data/lib/henshin/plugins/textile.rb +14 -16
  19. data/lib/henshin/post.rb +67 -120
  20. data/lib/henshin/site.rb +154 -131
  21. data/lib/henshin/static.rb +10 -8
  22. data/test/helper.rb +20 -8
  23. data/test/site/css/{includes/reset.sass → _reset.sass} +0 -0
  24. data/test/site/css/screen.sass +1 -1
  25. data/test/site/index.html +2 -3
  26. data/test/site/layouts/archive_date.html +5 -4
  27. data/test/site/layouts/archive_month.html +10 -5
  28. data/test/site/layouts/archive_year.html +13 -6
  29. data/test/site/layouts/category_page.html +7 -7
  30. data/test/site/layouts/main.html +3 -4
  31. data/test/site/layouts/post.html +1 -1
  32. data/test/site/layouts/tag_index.html +3 -2
  33. data/test/site/layouts/tag_page.html +6 -6
  34. data/test/site/options.yaml +2 -2
  35. data/test/site/plugins/test.rb +1 -3
  36. data/test/site/posts/Testing-Stuff.markdown +1 -1
  37. data/test/site/posts/Textile-Test.textile +1 -1
  38. data/test/site/posts/lorem-ipsum.markdown +1 -1
  39. data/test/site/posts/same-date.markdown +1 -1
  40. data/test/{test_henshin.rb → suite.rb} +0 -1
  41. data/test/test_gen.rb +98 -0
  42. data/test/test_options.rb +34 -19
  43. data/test/test_post.rb +67 -0
  44. data/test/test_site.rb +159 -46
  45. data/test/test_static.rb +13 -0
  46. metadata +53 -32
  47. data/lib/henshin/categories.rb +0 -29
  48. data/lib/henshin/tags.rb +0 -28
  49. data/test/test_archives.rb +0 -27
  50. data/test/test_categories.rb +0 -0
  51. data/test/test_gens.rb +0 -54
  52. data/test/test_layouts.rb +0 -14
  53. data/test/test_posts.rb +0 -75
  54. data/test/test_statics.rb +0 -0
  55. data/test/test_tags.rb +0 -0
  56. data/test/text_exts.rb +0 -0
File without changes
@@ -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
- # Gets the extension from a string
62
- def extension
63
- parts = self.split('.')
64
- parts[parts.size-1]
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
- # Gets the directory from a string
68
- def directory
69
- self =~ /((\.?\/?[a-zA-Z0-9 _-]+\/)+)/
70
- $1
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
- # Gets the filename from a string
74
- def file_name
75
- self.dup.gsub(/([a-zA-Z0-9_-]+\/)/, '')
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
@@ -1,133 +1,148 @@
1
1
  module Henshin
2
2
 
3
- # This is the main class for all pages, posts, sass, etc, that need to be run through a plugin
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, :extension, :content, :layout, :title
7
- attr_accessor :site, :config, :renderer, :data, :output
6
+ attr_accessor :path, :data, :content, :site, :to_inject, :generators
8
7
 
9
- def initialize( path, site, data=nil )
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
- @config = site.config
13
- @extension = path.extension
14
- @data = data
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
- # Processes the file
20
- def process
21
- self.read_yaml
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
- # Reads the files yaml frontmatter and uses it to override some settings, then grabs content
25
- def read_yaml
26
- file = File.read(self.path)
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).to_options
30
- self.override(override)
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
- # Uses the loaded data to override settings
38
- #
39
- # @param [Hash] override data to override settings with
40
- def override( override )
41
- @title = override[:title] if override[:title]
42
- @layout = @site.layouts[ override[:layout] ] if override[:layout]
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
- ignore_layout = false
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
- @layout ||= site.layouts[ site.config[:layout] ]
66
- unless ignore_layout || @layout.nil?
67
- config[:plugins][:layout_parsers].each do |plugin|
68
- @content = plugin.generate( @layout, self.payload )
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[ @data[:name] ] = @data[:payload] if @data
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 post data into a hash
105
+ # Turns all of the gens data into a hash.
90
106
  #
91
107
  # @return [Hash]
92
108
  def to_hash
93
- {
94
- 'title' => @title,
95
- 'permalink' => self.permalink,
96
- 'url' => self.url,
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
- write_path = File.join( config[:root], config[:target], @path[config[:root].size..-1] )
106
-
107
- # change extension if necessary
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
- # Returns the permalink for the gen
124
+ # @return [String] the permalink of the gen
116
125
  def permalink
117
- @path[config[:root].size..-1]
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
- # Returns the (pretty) url for the gen
131
+ # @return [String] the pretty url for the gen
121
132
  def url
122
- if config[:permalink].include? "/index.html"
123
- self.permalink[0..-11]
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
- # Needed to sort the posts by date, newest first
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