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
data/lib/henshin.rb
CHANGED
@@ -1,50 +1,175 @@
|
|
1
|
-
|
1
|
+
$: << File.dirname(__FILE__)
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'attr_plus/class'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'hashie/mash'
|
6
|
+
require 'highline'
|
7
|
+
require 'tilt'
|
4
8
|
require 'yaml'
|
5
|
-
require 'pp'
|
6
|
-
require 'pathname'
|
7
9
|
|
8
|
-
require '
|
9
|
-
require '
|
10
|
+
require 'slim'
|
11
|
+
require 'redcarpet'
|
12
|
+
Object.send(:remove_const, :RedcarpetCompat) if defined?(::RedcarpetCompat)
|
10
13
|
|
11
|
-
require 'henshin/
|
12
|
-
require 'henshin/
|
14
|
+
require 'henshin/error'
|
15
|
+
require 'henshin/safety'
|
16
|
+
|
17
|
+
require 'henshin/compressor'
|
18
|
+
require 'henshin/compressors/css'
|
19
|
+
require 'henshin/compressors/js'
|
20
|
+
|
21
|
+
require 'henshin/core_ext'
|
22
|
+
require 'henshin/path'
|
23
|
+
require 'henshin/scope'
|
24
|
+
|
25
|
+
require 'henshin/writer'
|
26
|
+
require 'henshin/publisher'
|
27
|
+
require 'henshin/publishers/sftp'
|
13
28
|
|
14
|
-
require 'henshin/
|
15
|
-
require 'henshin/
|
16
|
-
require 'henshin/
|
29
|
+
require 'henshin/file'
|
30
|
+
require 'henshin/files/attributes'
|
31
|
+
require 'henshin/files/abstract'
|
32
|
+
require 'henshin/files/physical'
|
33
|
+
require 'henshin/files/templatable'
|
17
34
|
|
18
|
-
require 'henshin/
|
19
|
-
require 'henshin/
|
20
|
-
require 'henshin/
|
35
|
+
require 'henshin/files/empty_template'
|
36
|
+
require 'henshin/files/post'
|
37
|
+
require 'henshin/files/template'
|
38
|
+
require 'henshin/files/tilt'
|
39
|
+
require 'henshin/files/tilt_template'
|
21
40
|
|
41
|
+
require 'henshin/package'
|
42
|
+
require 'henshin/packages/script'
|
43
|
+
require 'henshin/packages/style'
|
44
|
+
|
45
|
+
require 'henshin/reader'
|
46
|
+
require 'henshin/site'
|
47
|
+
require 'henshin/ui'
|
48
|
+
require 'henshin/version'
|
22
49
|
|
23
50
|
module Henshin
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# @
|
46
|
-
|
47
|
-
|
51
|
+
extend self
|
52
|
+
|
53
|
+
# Loads the yaml text given returning a Hash with symbol keys.
|
54
|
+
#
|
55
|
+
# @param text [String]
|
56
|
+
# @return [Hash{Symbol=>Object}]
|
57
|
+
def load_yaml(text)
|
58
|
+
(YAML.load(text) || {}).symbolise
|
59
|
+
end
|
60
|
+
|
61
|
+
SETTINGS = {
|
62
|
+
colour: true,
|
63
|
+
dry_run: false,
|
64
|
+
klass: Site,
|
65
|
+
local: false,
|
66
|
+
profile: false,
|
67
|
+
quiet: false
|
68
|
+
}
|
69
|
+
|
70
|
+
# Sets a global Henshin setting.
|
71
|
+
#
|
72
|
+
# @param key [Symbol]
|
73
|
+
# @example
|
74
|
+
#
|
75
|
+
# Henshin.set :colour
|
76
|
+
# Henshin.colour? #=> true
|
77
|
+
#
|
78
|
+
def set(key)
|
79
|
+
SETTINGS[key] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Unsets a global Henshin setting.
|
83
|
+
#
|
84
|
+
# @param key [Symbol]
|
85
|
+
# @example
|
86
|
+
#
|
87
|
+
# Henshin.unset :colour
|
88
|
+
# Henshin.colour? #=> false
|
89
|
+
#
|
90
|
+
def unset(key)
|
91
|
+
SETTINGS[key] = false
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return Whether to display colourful output.
|
95
|
+
def colour?
|
96
|
+
SETTINGS[:colour]
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return Whether to write files to disk.
|
100
|
+
def dry_run?
|
101
|
+
SETTINGS[:dry_run]
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return Whether to use local referencing urls.
|
105
|
+
# @note This is very much work in progress and does break!
|
106
|
+
def local?
|
107
|
+
SETTINGS[:local]
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return Whether to calculate profiling data.
|
111
|
+
def profile?
|
112
|
+
SETTINGS[:profile]
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return Whether to only show vital output.
|
116
|
+
def quiet?
|
117
|
+
SETTINGS[:quiet]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Set the Site class that is used to build the site.
|
121
|
+
#
|
122
|
+
# @param klass [Class]
|
123
|
+
# @example
|
124
|
+
#
|
125
|
+
# class MyCoolSite < Henshin::Site
|
126
|
+
# # ...
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# Henshin.use MyCoolSite
|
130
|
+
#
|
131
|
+
def use(klass)
|
132
|
+
SETTINGS[:klass] = klass
|
133
|
+
end
|
134
|
+
|
135
|
+
# Evaluates the +init.rb+ file if it exists.
|
136
|
+
#
|
137
|
+
# @param root [Pathname] Root of the site.
|
138
|
+
def eval_init(root)
|
139
|
+
if (root + 'init.rb').exist?
|
140
|
+
eval (root + 'init.rb').read
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Builds the site in +root+.
|
145
|
+
#
|
146
|
+
# @param root [Pathname]
|
147
|
+
def build(root, opts={})
|
148
|
+
time = Time.now if profile?
|
149
|
+
|
150
|
+
site = SETTINGS[:klass].new(root)
|
151
|
+
writer = Writer.new(site.dest)
|
152
|
+
site.write(writer)
|
153
|
+
|
154
|
+
puts "#{Time.now - time}s to build site." if profile?
|
155
|
+
end
|
156
|
+
|
157
|
+
# Publishes the site in +root+.
|
158
|
+
#
|
159
|
+
# @param root [Pathname]
|
160
|
+
def publish(root, opts={})
|
161
|
+
time = Time.now if profile?
|
162
|
+
|
163
|
+
site = SETTINGS[:klass].new(root)
|
164
|
+
|
165
|
+
unless site.config.has_key?(:publish)
|
166
|
+
UI.fail "No publish configuration in config.yml."
|
167
|
+
end
|
168
|
+
|
169
|
+
writer = Publisher::Sftp.create(site.config[:publish])
|
170
|
+
site.write(writer)
|
171
|
+
|
172
|
+
puts "#{Time.now - time}s to publish site." if profile?
|
48
173
|
end
|
49
174
|
|
50
175
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# @abstract You need to implement {#compress}.
|
4
|
+
#
|
5
|
+
# Compresses a set of files into one big ol' ugly file.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# compressed = Compressor.new(files)
|
10
|
+
# compressed.compress #=> "..."
|
11
|
+
#
|
12
|
+
class Compressor
|
13
|
+
|
14
|
+
# @param files [Array<File>]
|
15
|
+
def initialize(files=[])
|
16
|
+
@files = files
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] Simply joins the contents of all +files+ into a single
|
20
|
+
# string separated by newlines.
|
21
|
+
def join
|
22
|
+
@files.map {|file| file.text }.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] The compressed text for the +files+.
|
26
|
+
def compress
|
27
|
+
join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yui/compressor'
|
2
|
+
|
3
|
+
module Henshin
|
4
|
+
|
5
|
+
class Compressor
|
6
|
+
# Compresses css files using the yui-compressor.
|
7
|
+
class Css < Compressor
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super
|
11
|
+
@compressor = YUI::CssCompressor.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] The compressed, joined text from the given css files.
|
15
|
+
def compress
|
16
|
+
@compressor.compress(join)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yui/compressor'
|
2
|
+
|
3
|
+
module Henshin
|
4
|
+
|
5
|
+
class Compressor
|
6
|
+
# Compresses js files using the yui-compressor.
|
7
|
+
class Js < Compressor
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super
|
11
|
+
@compressor = YUI::JavaScriptCompressor.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] The compressed, joined text from the given js files.
|
15
|
+
def compress
|
16
|
+
@compressor.compress(join)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Changes all keys to symbols, works recursively.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
#
|
7
|
+
# {"a" => 1, "b" => 2}.symbolise
|
8
|
+
# #=> {:a => 1, :b => 2}
|
9
|
+
#
|
10
|
+
# @return [Hash]
|
11
|
+
def symbolise
|
12
|
+
each_with_object({}) do |(k, v), h|
|
13
|
+
h[k.to_sym] = (v.respond_to?(:symbolise) ? v.symbolise : v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Merges the hash given with this hash but deeply.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
#
|
21
|
+
# a = {:person => {:name => "John", :age => 30}}
|
22
|
+
# b = {:person => {:age => 31, :job => "Dummy"}}
|
23
|
+
#
|
24
|
+
# a.merge(b)
|
25
|
+
# #=> {:person => {:age => 31, :job => "Dummy"}}
|
26
|
+
#
|
27
|
+
# a.deep_merge(b)
|
28
|
+
# #=> {:person => {:name => "John", :age => 31, :job => "Dummy"}}
|
29
|
+
#
|
30
|
+
# @param other [Hash]
|
31
|
+
# @see http://timelessrepo.com/when-in-doubt
|
32
|
+
def deep_merge(other)
|
33
|
+
m = proc {|_,o,n| o.respond_to?(:merge) ? o.merge(n, &m) : n }
|
34
|
+
merge(other, &m)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Pathname
|
43
|
+
def same_type?(other)
|
44
|
+
if relative?
|
45
|
+
other.relative?
|
46
|
+
else
|
47
|
+
other.absolute?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class String
|
53
|
+
|
54
|
+
# Converts the string to a format suitable for use as a url.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
#
|
58
|
+
# "Hey, Wait! I've got this string.".slugify
|
59
|
+
# #=> "hey-wait-ive-got-this-string"
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def slugify
|
63
|
+
gsub(/[']+/, '').
|
64
|
+
gsub(/\W+/, ' ').
|
65
|
+
strip.
|
66
|
+
downcase.
|
67
|
+
gsub(' ', '-')
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# As most errors will be shown to a user of Henshin, they don't care about
|
4
|
+
# large backtraces. Using {.prettify} limits the backtrace and displays the
|
5
|
+
# message in an easier to read way.
|
6
|
+
class Error
|
7
|
+
|
8
|
+
LIMIT = 3
|
9
|
+
|
10
|
+
# @param msg [String] Message describing the error
|
11
|
+
# @param err [Exception] The exception raised
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# begin
|
15
|
+
# # an exception is raised
|
16
|
+
# rescue => err
|
17
|
+
# Error.prettify 'Something went wrong', err
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
def self.prettify(msg, err)
|
21
|
+
puts "\n#{msg}".red.bold
|
22
|
+
puts " #{err.message}"
|
23
|
+
puts err.backtrace.take(LIMIT).map {|l| " #{l}" }.join("\n")
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/henshin/file.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Henshin
|
2
|
+
|
3
|
+
# A Factory class which abstracts away the process of creating File (subclass)
|
4
|
+
# objects.
|
5
|
+
#
|
6
|
+
# File types can be registered using {.register} and then the
|
7
|
+
# appropriate class will be used when {.create} is used. You can also add
|
8
|
+
# functionality that is meant for broader use than a specific file type using
|
9
|
+
# modules, these can be registered using the {.apply} method and when
|
10
|
+
# {.create} is called the File will be extended with the correct modules.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# class MyFileType < Henshin::File::Physical
|
15
|
+
# # override some methods...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Henshin::File.register /\.abc\Z/, MyFileType
|
19
|
+
#
|
20
|
+
# module SomeFileRole
|
21
|
+
# # define some methods...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Henshin::File.apply %r{(^|/)special_files/}, SomeFileRole
|
25
|
+
#
|
26
|
+
# file = Henshin::File.create(site, Pathname.new('special_files/test.abc')
|
27
|
+
#
|
28
|
+
# file.class #=> MyFileType
|
29
|
+
# file.singleton_class.include?(SomeFileRole) #=> true
|
30
|
+
#
|
31
|
+
class File
|
32
|
+
|
33
|
+
@types = []
|
34
|
+
@applies = []
|
35
|
+
|
36
|
+
# Registers a new file type which can then be used by {.create}.
|
37
|
+
#
|
38
|
+
# @param match [#match] Regexp path must match to be +klass+ type
|
39
|
+
# @param klass [Class] Subclass of File
|
40
|
+
def self.register(match, klass)
|
41
|
+
@types.unshift [match, klass]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Registers a module to be applied to files which have a path matching
|
45
|
+
# +match+.
|
46
|
+
#
|
47
|
+
# @param match [#match] Regexp path must match to extend +mod+
|
48
|
+
# @param mod [Module]
|
49
|
+
def self.apply(match, mod)
|
50
|
+
@applies.unshift [match, mod]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a new File, or if possible a subclass of File, depending on the
|
54
|
+
# extension of the path given.
|
55
|
+
#
|
56
|
+
# @param site [Site]
|
57
|
+
# @param path [Pathname]
|
58
|
+
def self.create(site, path)
|
59
|
+
klass = (@types.find {|k,v| k =~ path.to_s } || [nil, File::Physical]).last
|
60
|
+
obj = klass.new(site, path)
|
61
|
+
|
62
|
+
@applies.find_all {|k,v| k =~ path.to_s }.each {|_,v| obj.extend(v) }
|
63
|
+
obj
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|