henshin 0.4.2 → 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE +18 -0
- data/README.md +133 -0
- data/Rakefile +6 -51
- data/bin/henshin +127 -134
- data/lib/henshin.rb +163 -38
- data/lib/henshin/compressor.rb +31 -0
- data/lib/henshin/compressors/css.rb +20 -0
- data/lib/henshin/compressors/js.rb +20 -0
- data/lib/henshin/core_ext.rb +69 -0
- data/lib/henshin/error.rb +28 -0
- data/lib/henshin/file.rb +67 -0
- data/lib/henshin/files/abstract.rb +102 -0
- data/lib/henshin/files/attributes.rb +31 -0
- data/lib/henshin/files/empty_template.rb +24 -0
- data/lib/henshin/files/physical.rb +117 -0
- data/lib/henshin/files/post.rb +64 -0
- data/lib/henshin/files/templatable.rb +29 -0
- data/lib/henshin/files/template.rb +45 -0
- data/lib/henshin/files/tilt.rb +50 -0
- data/lib/henshin/files/tilt_template.rb +45 -0
- data/lib/henshin/package.rb +27 -0
- data/lib/henshin/packages/script.rb +23 -0
- data/lib/henshin/packages/style.rb +20 -0
- data/lib/henshin/path.rb +143 -0
- data/lib/henshin/publisher.rb +42 -0
- data/lib/henshin/publishers/sftp.rb +79 -0
- data/lib/henshin/reader.rb +68 -0
- data/lib/henshin/safety.rb +74 -0
- data/lib/henshin/scope.rb +55 -0
- data/lib/henshin/site.rb +291 -228
- data/lib/henshin/ui.rb +35 -0
- data/lib/henshin/version.rb +3 -0
- data/lib/henshin/writer.rb +43 -0
- data/lib/rack/henshin.rb +113 -0
- data/site/assets/scripts/config.js +11 -0
- data/site/assets/styles/_mixins.sass +16 -0
- data/site/assets/styles/screen.sass +198 -0
- data/site/config.yml +13 -0
- data/site/drafts/a-work-in-progress.md +5 -0
- data/site/feed.xml.slim +19 -0
- data/site/index.html.slim +28 -0
- data/site/init.rb +33 -0
- data/site/posts/1-hello-world.md +46 -0
- data/site/posts/2-code-testing.md +36 -0
- data/site/posts/3-style-test.md +80 -0
- data/site/templates/default.slim +11 -0
- data/site/templates/post.slim +30 -0
- data/site/test.html.md +7 -0
- data/spec/helper.rb +70 -0
- data/spec/henshin/compressor_spec.rb +25 -0
- data/spec/henshin/compressors/css_spec.rb +22 -0
- data/spec/henshin/compressors/js_spec.rb +22 -0
- data/spec/henshin/core_ext_spec.rb +59 -0
- data/spec/henshin/error_spec.rb +22 -0
- data/spec/henshin/file_spec.rb +28 -0
- data/spec/henshin/files/abstract_spec.rb +98 -0
- data/spec/henshin/files/attributes_spec.rb +25 -0
- data/spec/henshin/files/empty_template_spec.rb +11 -0
- data/spec/henshin/files/physical_spec.rb +55 -0
- data/spec/henshin/files/post_spec.rb +66 -0
- data/spec/henshin/files/template_spec.rb +53 -0
- data/spec/henshin/files/tilt_spec.rb +59 -0
- data/spec/henshin/package_spec.rb +24 -0
- data/spec/henshin/packages/script_spec.rb +17 -0
- data/spec/henshin/packages/style_spec.rb +17 -0
- data/spec/henshin/path_spec.rb +56 -0
- data/spec/henshin/publisher_spec.rb +44 -0
- data/spec/henshin/publishers/sftp_spec.rb +21 -0
- data/spec/henshin/reader_spec.rb +55 -0
- data/spec/henshin/safety_spec.rb +66 -0
- data/spec/henshin/scope_spec.rb +33 -0
- data/spec/henshin/site_spec.rb +142 -0
- data/spec/henshin/ui_spec.rb +26 -0
- data/spec/henshin/writer_spec.rb +26 -0
- metadata +352 -197
- data/.gitignore +0 -27
- data/LICENSE +0 -20
- data/README.markdown +0 -35
- data/VERSION +0 -1
- data/henshin.gemspec +0 -121
- data/lib/henshin/archive.rb +0 -133
- data/lib/henshin/exec/files.rb +0 -46
- data/lib/henshin/ext.rb +0 -55
- data/lib/henshin/gen.rb +0 -154
- data/lib/henshin/labels.rb +0 -144
- data/lib/henshin/plugin.rb +0 -100
- data/lib/henshin/plugins/highlight.rb +0 -24
- data/lib/henshin/plugins/liquid.rb +0 -61
- data/lib/henshin/plugins/maruku.rb +0 -18
- data/lib/henshin/plugins/sass.rb +0 -24
- data/lib/henshin/plugins/textile.rb +0 -18
- data/lib/henshin/post.rb +0 -156
- data/lib/henshin/static.rb +0 -33
- data/test/helper.rb +0 -44
- data/test/site/css/_reset.sass +0 -34
- data/test/site/css/print.css +0 -12
- data/test/site/css/screen.sass +0 -70
- data/test/site/includes/head.html +0 -1
- data/test/site/index.html +0 -23
- data/test/site/layouts/archive_date.html +0 -20
- data/test/site/layouts/archive_month.html +0 -24
- data/test/site/layouts/archive_year.html +0 -26
- data/test/site/layouts/category_index.html +0 -27
- data/test/site/layouts/category_page.html +0 -20
- data/test/site/layouts/main.html +0 -13
- data/test/site/layouts/post.html +0 -36
- data/test/site/layouts/tag_index.html +0 -27
- data/test/site/layouts/tag_page.html +0 -20
- data/test/site/options.yaml +0 -17
- data/test/site/plugins/test.rb +0 -3
- data/test/site/posts/Testing-Stuff.markdown +0 -14
- data/test/site/posts/Textile-Test.textile +0 -7
- data/test/site/posts/cat/test.markdown +0 -6
- data/test/site/posts/lorem-ipsum.markdown +0 -7
- data/test/site/posts/same-date.markdown +0 -7
- data/test/site/static.html +0 -19
- data/test/suite.rb +0 -4
- data/test/test_gen.rb +0 -98
- data/test/test_options.rb +0 -73
- data/test/test_post.rb +0 -67
- data/test/test_site.rb +0 -197
- data/test/test_static.rb +0 -13
@@ -0,0 +1,102 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# @abstract You will want to implement {#raw_text}, {#path} and maybe
|
6
|
+
# {#text}.
|
7
|
+
#
|
8
|
+
# This class implements all the functionality that is required to build or
|
9
|
+
# serve a file. {Abstract} instances do not relate to a file in the file
|
10
|
+
# system, use {Physical} in this case.
|
11
|
+
class Abstract
|
12
|
+
|
13
|
+
include Henshin::Helpers, Safety, Comparable
|
14
|
+
extend Attributes
|
15
|
+
|
16
|
+
attr_reader :site
|
17
|
+
|
18
|
+
def initialize(site)
|
19
|
+
@site = site.safe
|
20
|
+
end
|
21
|
+
|
22
|
+
# Simple version of text. This is raw in that it has not been run through
|
23
|
+
# a template. It may still have been passed through an ordinary rendering
|
24
|
+
# engine (for instance a Markdown engine).
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
# @see #text
|
28
|
+
def raw_text
|
29
|
+
""
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] Text to write to the file.
|
33
|
+
def text
|
34
|
+
raw_text
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Path] Path to the file.
|
38
|
+
def path
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String] The absolute url to the file.
|
43
|
+
def permalink
|
44
|
+
path.permalink
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Pathname] A pretty url to the file, the permalink with
|
48
|
+
# 'index.html' stripped from the end generally.
|
49
|
+
def url
|
50
|
+
path.url
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] Extension for the file to be written.
|
54
|
+
def extension
|
55
|
+
path.extension
|
56
|
+
end
|
57
|
+
|
58
|
+
# Allow template to be set, needed for Template to work properly.
|
59
|
+
def template
|
60
|
+
@template || nil
|
61
|
+
end
|
62
|
+
attr_writer :template
|
63
|
+
|
64
|
+
# Writes the file.
|
65
|
+
#
|
66
|
+
# @param writer [#write] Object which is able to write text to a path.
|
67
|
+
def write(writer)
|
68
|
+
return unless writeable?
|
69
|
+
start = Time.now if Henshin.profile?
|
70
|
+
writer.write Pathname.new(permalink.sub(/^\//, '')), text
|
71
|
+
if Henshin.profile?
|
72
|
+
UI.wrote permalink, (Time.now - start)
|
73
|
+
else
|
74
|
+
UI.wrote permalink
|
75
|
+
end
|
76
|
+
rescue => e
|
77
|
+
Error.prettify("Error writing #{inspect}", e)
|
78
|
+
end
|
79
|
+
|
80
|
+
unsafe :write
|
81
|
+
|
82
|
+
# Compares the files based on their permalinks.
|
83
|
+
#
|
84
|
+
# @param other [File]
|
85
|
+
def <=>(other)
|
86
|
+
permalink <=> other.permalink
|
87
|
+
end
|
88
|
+
|
89
|
+
def inspect
|
90
|
+
"#<#{self.class} #{permalink}>"
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# @return Whether this file should be written.
|
96
|
+
def writeable?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Henshin
|
4
|
+
|
5
|
+
class File
|
6
|
+
|
7
|
+
# Extend any module (classes will generally be inheriting from {Abstract} so
|
8
|
+
# will pick this up) with this module to gain the ability to set required
|
9
|
+
# yaml keys and default templates.
|
10
|
+
module Attributes
|
11
|
+
|
12
|
+
def requires(*keys)
|
13
|
+
@required ||= Set.new
|
14
|
+
@required += keys
|
15
|
+
end
|
16
|
+
|
17
|
+
def required
|
18
|
+
@required || Set.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def template(name)
|
22
|
+
@template = name
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_template
|
26
|
+
@template
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# A file physically located on the file system. A subclass of file will
|
6
|
+
# have a @path variable with it's location.
|
7
|
+
class Physical < Abstract
|
8
|
+
|
9
|
+
# Regular expression to match the text of the file, contains two match
|
10
|
+
# groups; the first matches the yaml part, the second any text.
|
11
|
+
YAML_REGEX = /\A---\n^(.*?)\n^---\n?(.*)\z/m
|
12
|
+
|
13
|
+
# @param site [Site] Site the file is in.
|
14
|
+
# @param path [Pathname] Path to the file.
|
15
|
+
def initialize(site, path)
|
16
|
+
@site = site.safe
|
17
|
+
@path = path
|
18
|
+
end
|
19
|
+
|
20
|
+
# Allow yaml attributes to be accessed in templates.
|
21
|
+
def method_missing(sym, *args, &block)
|
22
|
+
if yaml.key?(sym)
|
23
|
+
yaml[sym]
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Allow template to be set, needed for Template to work properly.
|
30
|
+
def template
|
31
|
+
@template || yaml[:template]
|
32
|
+
end
|
33
|
+
|
34
|
+
def yield
|
35
|
+
text
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] Text of the file.
|
39
|
+
def raw_text
|
40
|
+
read[1]
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Path] If a permalink has been set in the yaml frontmatter uses
|
44
|
+
# that, otherwise uses the path to the file.
|
45
|
+
def path
|
46
|
+
if yaml.key?(:permalink)
|
47
|
+
if yaml[:permalink].start_with?('/')
|
48
|
+
Path @site.root, yaml[:permalink][1..-1]
|
49
|
+
else
|
50
|
+
Path @site.root, yaml[:permalink]
|
51
|
+
end
|
52
|
+
|
53
|
+
else
|
54
|
+
rel = @path
|
55
|
+
|
56
|
+
if @path.same_type?(@site.source)
|
57
|
+
rel = @path.relative_path_from(@site.source)
|
58
|
+
end
|
59
|
+
|
60
|
+
if @path.basename.to_s.count('.') == 1
|
61
|
+
Path @site.root, rel
|
62
|
+
else
|
63
|
+
ext = @path.extname
|
64
|
+
file = rel.to_s[0..-ext.size-1]
|
65
|
+
Path @site.root, file
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Reads the file, splitting it in to two parts; the yaml and the text.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
#
|
76
|
+
# file = File.new(site, "hello-world.md")
|
77
|
+
# file.read
|
78
|
+
# #=> ["title: Hello World\ndate: 2012-02-03",
|
79
|
+
# # "Hello, world!"]
|
80
|
+
#
|
81
|
+
# @return [Array<String>] An array of two parts. The first is the yaml part
|
82
|
+
# of the file, the second is the text part.
|
83
|
+
def read
|
84
|
+
contents = @path.read || ""
|
85
|
+
if match = contents.match(YAML_REGEX)
|
86
|
+
match.to_a[1..2]
|
87
|
+
else
|
88
|
+
['', contents]
|
89
|
+
end
|
90
|
+
rescue
|
91
|
+
['', contents]
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Hash{Symbol=>Object}] Returns the data loaded from the file's
|
95
|
+
# yaml frontmatter.
|
96
|
+
def yaml
|
97
|
+
loaded = Henshin.load_yaml read[0]
|
98
|
+
|
99
|
+
singleton_class.ancestors.find_all {|klass|
|
100
|
+
klass.singleton_class.include?(Attributes)
|
101
|
+
|
102
|
+
}.map {|klass|
|
103
|
+
klass.required.to_a
|
104
|
+
|
105
|
+
}.flatten.reject {|key|
|
106
|
+
respond_to?(key) || loaded.key?(key)
|
107
|
+
|
108
|
+
}.each {|key|
|
109
|
+
UI.fail(inspect + " requires #{key}.")
|
110
|
+
}
|
111
|
+
|
112
|
+
loaded
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
# A post, stored in the /posts folder.
|
5
|
+
module Post
|
6
|
+
include Templatable
|
7
|
+
extend Attributes
|
8
|
+
|
9
|
+
requires :title, :date
|
10
|
+
template 'post'
|
11
|
+
|
12
|
+
def rss_date
|
13
|
+
date.rfc2822
|
14
|
+
end
|
15
|
+
|
16
|
+
def next=(post)
|
17
|
+
@next = post
|
18
|
+
end
|
19
|
+
|
20
|
+
def prev=(post)
|
21
|
+
@prev = post
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_post
|
25
|
+
@next
|
26
|
+
end
|
27
|
+
|
28
|
+
def prev_post
|
29
|
+
@prev
|
30
|
+
end
|
31
|
+
|
32
|
+
def published?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
style = @site.config[:permalink]
|
38
|
+
|
39
|
+
data = {
|
40
|
+
year: date.strftime('%Y'),
|
41
|
+
month: date.strftime('%m'),
|
42
|
+
day: date.strftime('%d'),
|
43
|
+
title: title.slugify
|
44
|
+
}
|
45
|
+
|
46
|
+
url = data.inject(style) {|res, (tok, val)|
|
47
|
+
res.gsub /:#{Regexp.escape(tok)}/, val
|
48
|
+
}
|
49
|
+
|
50
|
+
Path @site.root, url[1..-1]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Compares posts on date, then on permalink if dates are the same.
|
54
|
+
def <=>(other)
|
55
|
+
c = other.respond_to?(:date) ? other.date <=> date : 0
|
56
|
+
c.zero? ? super : c
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
apply %r{(^|/)posts/}, Post
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
module Templatable
|
6
|
+
|
7
|
+
# Overrides #text in the included class so that the result from it is passed
|
8
|
+
# into a template. Uses the template set with .template, or if not found, the
|
9
|
+
# default template.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
def text
|
13
|
+
res = raw_text
|
14
|
+
data = safe
|
15
|
+
|
16
|
+
return res if data.template == "none"
|
17
|
+
|
18
|
+
data.singleton_class.send(:define_method, :text) { res }
|
19
|
+
|
20
|
+
default = nil
|
21
|
+
singleton_class.ancestors.find {|klass|
|
22
|
+
default = klass.default_template if klass.respond_to?(:default_template)
|
23
|
+
}
|
24
|
+
|
25
|
+
@site.template(default, Henshin::DEFAULT_TEMPLATE).render(data)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# Used to extend template files. These will generally be written in a language
|
6
|
+
# such as slim, as they have the ability to include data.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# t = SlimFile.new(site, path)
|
11
|
+
# t.extend Template
|
12
|
+
#
|
13
|
+
# t.template other_file
|
14
|
+
# #=> "..."
|
15
|
+
#
|
16
|
+
module Template
|
17
|
+
|
18
|
+
# Name used to refer to the template.
|
19
|
+
# @return [String]
|
20
|
+
def name
|
21
|
+
@path.basename.to_s.split('.').first
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets the data and then uses the superclasses #text method to render the
|
25
|
+
# template.
|
26
|
+
#
|
27
|
+
# @param data [Hash]
|
28
|
+
def template(data)
|
29
|
+
data.template = 'none'
|
30
|
+
@data = data.safe
|
31
|
+
text
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :render, :template
|
35
|
+
|
36
|
+
# @return [Hash] The data set by #template.
|
37
|
+
def data
|
38
|
+
@data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
apply %r{(^|/)templates/}, Template
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# Renders a file using an engine from Tilt. This particular class deals
|
6
|
+
# with file types which would never use templates.
|
7
|
+
#
|
8
|
+
# @see http://github.com/rtomayko/tilt
|
9
|
+
class Tilt < Physical
|
10
|
+
|
11
|
+
EXTENSIONS = %w(sass scss less coffee).map(&:to_sym)
|
12
|
+
|
13
|
+
# Renders the files text using the appropriate Tilt engine. Also gets the
|
14
|
+
# configuration for the engine, if any, from {Site#config}.
|
15
|
+
#
|
16
|
+
# @return [String] The rendered file contents
|
17
|
+
def raw_text
|
18
|
+
ext = @path.extname[1..-1].to_sym
|
19
|
+
config = (@site.config[ext] || {}).to_hash.symbolise
|
20
|
+
::Tilt[ext].new(nil, nil, config) { super }.render
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
register /\.(#{Tilt::EXTENSIONS.join('|')})\Z/, Tilt
|
25
|
+
|
26
|
+
|
27
|
+
# Renders a file using an engine from Tilt. This class, unlike {Tilt} deals
|
28
|
+
# with file types which generally would use a template. To prevent a
|
29
|
+
# template being used, you need to add to the YAML frontmatter:
|
30
|
+
#
|
31
|
+
# template: none
|
32
|
+
#
|
33
|
+
# And to use a different template than 'default' simply use the template's
|
34
|
+
# name instead of "none". For example, to use the template "page" I would
|
35
|
+
# add:
|
36
|
+
#
|
37
|
+
# template: page
|
38
|
+
#
|
39
|
+
class TiltWithTemplate < Tilt
|
40
|
+
include Templatable
|
41
|
+
|
42
|
+
EXTENSIONS = %w(str markdown mkd md textile rdoc wiki
|
43
|
+
creaole mediawiki mw).map(&:to_sym)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
register /\.(#{TiltWithTemplate::EXTENSIONS.join('|')})\Z/, TiltWithTemplate
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|