henshin 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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