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,45 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
class TiltTemplate < Physical
|
6
|
+
|
7
|
+
EXTENSIONS = %w(erb rhtml erubis
|
8
|
+
haml
|
9
|
+
nokogiri
|
10
|
+
builder
|
11
|
+
mab
|
12
|
+
liquid
|
13
|
+
radius
|
14
|
+
slim
|
15
|
+
yajl).map(&:to_sym)
|
16
|
+
|
17
|
+
def data
|
18
|
+
self.safe
|
19
|
+
end
|
20
|
+
|
21
|
+
def text
|
22
|
+
ext = @path.extname[1..-1].to_sym
|
23
|
+
scope = data
|
24
|
+
|
25
|
+
text = ::Tilt[ext].new(nil, nil, (@site.config[ext] || {}).to_hash.symbolise) {
|
26
|
+
raw_text
|
27
|
+
}.render(scope) { scope.yield }
|
28
|
+
|
29
|
+
return text if scope.template == 'none'
|
30
|
+
|
31
|
+
default = nil
|
32
|
+
singleton_class.ancestors.find {|klass|
|
33
|
+
default = klass.default_template if klass.respond_to?(:default_template)
|
34
|
+
}
|
35
|
+
|
36
|
+
templates = [scope.template, default, Henshin::DEFAULT_TEMPLATE].compact
|
37
|
+
|
38
|
+
@site.template(*templates).render(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
register /\.(#{TiltTemplate::EXTENSIONS.join('|')})\Z/, TiltTemplate
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# @abstract Must implement {#path}.
|
4
|
+
# Concatenates multiple files into one, compressed file.
|
5
|
+
class Package < File::Abstract
|
6
|
+
|
7
|
+
# @param site [Site]
|
8
|
+
# @param compressor [Compressor]
|
9
|
+
def initialize(site, compressor)
|
10
|
+
@site = site
|
11
|
+
@compressor = compressor
|
12
|
+
end
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String]
|
19
|
+
def text
|
20
|
+
enabled? ? @compressor.compress : @compressor.join
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
# Path site, '...'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class Package
|
4
|
+
# Concatenates script files (java/coffee-script) then minifies them.
|
5
|
+
class Script < Package
|
6
|
+
|
7
|
+
# @param site [Site]
|
8
|
+
# @param paths [Array<Pathname>]
|
9
|
+
def initialize(site, paths)
|
10
|
+
compressor = Compressor::Js.new(paths.map {|p| File.create(site, p) })
|
11
|
+
super(site, compressor)
|
12
|
+
end
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
@site.config[:compress][:scripts]
|
16
|
+
end
|
17
|
+
|
18
|
+
def path
|
19
|
+
Path @site.root, 'script.js'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class Package
|
4
|
+
class Style < Package
|
5
|
+
|
6
|
+
def initialize(site, paths)
|
7
|
+
compressor = Compressor::Css.new(paths.map {|p| File.create(site, p) })
|
8
|
+
super(site, compressor)
|
9
|
+
end
|
10
|
+
|
11
|
+
def enabled?
|
12
|
+
@site.config[:compress][:styles]
|
13
|
+
end
|
14
|
+
|
15
|
+
def path
|
16
|
+
Path @site.root, 'style.css'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/henshin/path.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# @example
|
4
|
+
#
|
5
|
+
# path = Path('/blog', 'tag', 'code', 'index.html')
|
6
|
+
# path.extension #=> 'html'
|
7
|
+
# path.permalink #=> '/blog/tag/code/index.html'
|
8
|
+
# path.url #=> '/blog/tag/code'
|
9
|
+
#
|
10
|
+
# path == '/blog/tag/code/'
|
11
|
+
# #=> true
|
12
|
+
#
|
13
|
+
class Path
|
14
|
+
|
15
|
+
# Creates a new Path instance for the site given with the path provided.
|
16
|
+
# The path is constructed from the parts passed in.
|
17
|
+
#
|
18
|
+
# @param path [String, Pathname] The path to the file the Path instance
|
19
|
+
# refers to.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
#
|
23
|
+
# Path.new('/', 'folder', 'another-folder', 'file.txt')
|
24
|
+
# #=> #<Henshin::Path /folder/another-folder/file.txt>
|
25
|
+
#
|
26
|
+
def initialize(*path)
|
27
|
+
@path = path.flatten.map {|p| Pathname.new(p) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] Extension of the Path
|
31
|
+
def extension
|
32
|
+
::File.extname @path.last
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a full url path to the location specified.
|
36
|
+
#
|
37
|
+
# @return [String] Full url to Path
|
38
|
+
# @example
|
39
|
+
#
|
40
|
+
# Path('/', 'blog', '2011', 'hello-world', 'index.html').permalink
|
41
|
+
# #=> '/blog/2011/hello-world/index.html'
|
42
|
+
#
|
43
|
+
def permalink
|
44
|
+
url = @path.inject(:+).to_s
|
45
|
+
|
46
|
+
Henshin.local? ? url[1..-1] : url
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :to_s, :permalink
|
50
|
+
|
51
|
+
# Returns the url without +index.html+ at the end if possible.
|
52
|
+
#
|
53
|
+
# @return [String] Pretty url to Path
|
54
|
+
# @example
|
55
|
+
#
|
56
|
+
# Path('/', 'blog', '2011', 'hello-world', 'index.html').permalink
|
57
|
+
# #=> '/blog/2011/hello-world/'
|
58
|
+
#
|
59
|
+
def url
|
60
|
+
if Henshin.local?
|
61
|
+
permalink
|
62
|
+
else
|
63
|
+
permalink.sub /index\.html$/, ''
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Appends a path onto the Path.
|
68
|
+
#
|
69
|
+
# @param other [String, Pathname]
|
70
|
+
# @return [Path]
|
71
|
+
# @example
|
72
|
+
#
|
73
|
+
# path = Path('folder')
|
74
|
+
# path << 'another' << 'file.txt'
|
75
|
+
# path #=> #<Henshin::Path folder/another/file.txt>
|
76
|
+
#
|
77
|
+
def << other
|
78
|
+
@path << other
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Checks (strictly) whether a Path instance is equal to another. This
|
83
|
+
# compares them using {#to_s}, so both Paths will need to have the same
|
84
|
+
# root _and_ path to be equal.
|
85
|
+
#
|
86
|
+
# @param other [Path]
|
87
|
+
# @example
|
88
|
+
#
|
89
|
+
# a = Path 'a', 'test.txt'
|
90
|
+
# b = Path 'b', 'test.txt'
|
91
|
+
#
|
92
|
+
# a == b
|
93
|
+
# #=> false
|
94
|
+
#
|
95
|
+
# a == Path('a', 'test.txt')
|
96
|
+
# #=> true
|
97
|
+
#
|
98
|
+
def == other
|
99
|
+
return false unless other.is_a?(Path)
|
100
|
+
self.to_s == other.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
# Checks (loosely) whether this Path instance is equal to the given
|
104
|
+
# argument. The argument can be another Path instance or a String, in which
|
105
|
+
# case the String is checked for equality against the {#url} and
|
106
|
+
# {#permalink}.
|
107
|
+
#
|
108
|
+
# @param other [Path, String]
|
109
|
+
# @example
|
110
|
+
#
|
111
|
+
# index = Path '/blog', 'index.html'
|
112
|
+
# index.permalink #=> '/blog/index.html'
|
113
|
+
# index.url #=> '/blog/'
|
114
|
+
#
|
115
|
+
# index === '/blog/index.html'
|
116
|
+
# index === '/blog/'
|
117
|
+
# index === '/blog'
|
118
|
+
#
|
119
|
+
def === other
|
120
|
+
case other
|
121
|
+
when String
|
122
|
+
url == other || url[0..-2] == other || permalink == other
|
123
|
+
when Path
|
124
|
+
self == other
|
125
|
+
else
|
126
|
+
false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def inspect
|
131
|
+
"#<#{self.class} #{to_s}>"
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
module Kernel
|
138
|
+
|
139
|
+
# @see Henshin::Path#initialize
|
140
|
+
def Path(*args)
|
141
|
+
Henshin::Path.new(*args)
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# Publishes a site to your server.
|
4
|
+
class Publisher < Writer
|
5
|
+
|
6
|
+
# @return [#write]
|
7
|
+
def self.create(opts={})
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
# Checks that +hash+ contains all keys in +list+, raises error if not.
|
12
|
+
#
|
13
|
+
# @param hash [Hash]
|
14
|
+
# @param list [Array]
|
15
|
+
def self.requires_keys(hash, list)
|
16
|
+
list.each do |key|
|
17
|
+
unless hash.key?(key)
|
18
|
+
UI.fail "Publish hash must contain :#{name} key."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_required_opt(hsh, name)
|
25
|
+
hsh[name] || UI.fail("Must give :#{name} option to publish.")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_password(hsh, name)
|
29
|
+
if hsh.key?(name)
|
30
|
+
val = hsh[name]
|
31
|
+
if val =~ /^\$(\w+)\s+(.*)$/
|
32
|
+
`#{$1} -c '#{$2}'`.chomp
|
33
|
+
else
|
34
|
+
val
|
35
|
+
end
|
36
|
+
else
|
37
|
+
HighLine.new.ask("Enter password: ") {|q| q.echo = false }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
|
3
|
+
module Henshin
|
4
|
+
|
5
|
+
class Publisher
|
6
|
+
|
7
|
+
# Publishes the site using sftp. Writes files directly to the server. Requires
|
8
|
+
# the following configuration in config.yml:
|
9
|
+
#
|
10
|
+
# publish:
|
11
|
+
# host: sftp.example.com
|
12
|
+
# base: /path/to/public
|
13
|
+
# user: your_username
|
14
|
+
# pass: your_password
|
15
|
+
#
|
16
|
+
# It's a bad idea writing down your password in plain text so you can set
|
17
|
+
# pass to be a shell command which returns your password, for example:
|
18
|
+
#
|
19
|
+
# publish:
|
20
|
+
# pass: $sh get-sftp-password
|
21
|
+
#
|
22
|
+
# would run +sh -c 'get-sftp-password'+.
|
23
|
+
#
|
24
|
+
class Sftp < Publisher
|
25
|
+
|
26
|
+
def self.create(opts={})
|
27
|
+
opts[:pass] = get_password(opts, :pass)
|
28
|
+
requires_keys(opts, [:host, :base, :user, :pass])
|
29
|
+
opts[:base] = Pathname.new(opts[:base])
|
30
|
+
|
31
|
+
sftp = nil
|
32
|
+
unless Henshin.dry_run?
|
33
|
+
sftp = Net::SFTP.start(opts[:host], opts[:user], password: opts[:pass])
|
34
|
+
end
|
35
|
+
|
36
|
+
new(sftp, opts[:base])
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param sftp [Net::SFTP::Session] Connected session to use for transfers
|
40
|
+
# @param root [Pathname] Directory to write Site into
|
41
|
+
def initialize(sftp, root)
|
42
|
+
@sftp = sftp
|
43
|
+
@root = root
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @param path [Pathname] Path to check
|
49
|
+
# @return Whether +path+ exists
|
50
|
+
def exist?(path)
|
51
|
+
@sftp.lstat!(path.to_s)
|
52
|
+
true
|
53
|
+
rescue Net::SFTP::StatusException
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates a directory at +dir+.
|
58
|
+
#
|
59
|
+
# @param dir [Pathname] Path of directory to create
|
60
|
+
def write_dir(dir)
|
61
|
+
dir.descend do |sub|
|
62
|
+
@sftp.mkdir!(sub) unless exist?(sub)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Writes the file at +path+ with the +contents+ given.
|
67
|
+
#
|
68
|
+
# @param path [Pathname] Path to file to be written
|
69
|
+
# @param contents [String] Text to write to the file
|
70
|
+
def write_file(path, contents)
|
71
|
+
file = @sftp.file
|
72
|
+
file.open(path.to_s, 'w') do |file|
|
73
|
+
file.puts contents.force_encoding('binary')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
class Reader
|
4
|
+
|
5
|
+
RESERVED_DIRS = %w(assets drafts posts templates build)
|
6
|
+
|
7
|
+
# @param root [Pathname] Path to read under.
|
8
|
+
def initialize(root)
|
9
|
+
@root = root
|
10
|
+
@ignore = ['config.yml', 'init.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds the list of files given to the list to be ignored.
|
14
|
+
#
|
15
|
+
# @param files [Array<String>]
|
16
|
+
def ignore(*files)
|
17
|
+
@ignore += files
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param path [String]
|
21
|
+
# @return Whether the path given is to be ignored. Returns true if the path
|
22
|
+
# is to be ignored, see #ignore; or if the path, or any directory above,
|
23
|
+
# begins with an underscore.
|
24
|
+
def ignore?(path)
|
25
|
+
path = Pathname.new(path).relative_path_from(@root)
|
26
|
+
|
27
|
+
path.ascend do |part|
|
28
|
+
return true if part.basename.to_s[0] == "_"
|
29
|
+
return true if @ignore.include?(part.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reads the files using the glob pattern given. Ignores files matching
|
34
|
+
# {#ignore?}.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
#
|
38
|
+
# reader.read('assets', '**', '*')
|
39
|
+
# #=> [#<Pathname:assets/styles/screen.sass>, ...]
|
40
|
+
#
|
41
|
+
# @param path [Array<String>]
|
42
|
+
# @return [Array<Pathname>]
|
43
|
+
def read(*path)
|
44
|
+
glob = path.flatten.inject(@root, :+)
|
45
|
+
Pathname.glob(glob).reject {|p| p.directory? || ignore?(p) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Reads all files under the path given.
|
49
|
+
#
|
50
|
+
# @param path [Array<String>]
|
51
|
+
# @return [Array<Pathname>]
|
52
|
+
def read_all(*path)
|
53
|
+
read(path << '*')
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :all, :read_all
|
57
|
+
|
58
|
+
# Reads all paths under the root this Reader was initialised with, but
|
59
|
+
# ignores all those under "reserved directories". These are directories used
|
60
|
+
# by henshin for posts, templates, etc.
|
61
|
+
#
|
62
|
+
# @return [Array<Pathname>]
|
63
|
+
def safe_paths
|
64
|
+
read_all('**') - read_all("{#{RESERVED_DIRS.join(',')}}", '**')
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|