laze 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +150 -0
- data/Rakefile +76 -0
- data/VERSION.yml +5 -0
- data/bin/laze +126 -0
- data/examples/website/includes/tagline.html +1 -0
- data/examples/website/input/contact/email.md +6 -0
- data/examples/website/input/css/screen.css +5 -0
- data/examples/website/input/css/test.less +6 -0
- data/examples/website/input/img/ruby.gif +0 -0
- data/examples/website/input/img/test.jpg +0 -0
- data/examples/website/input/img/test.png +0 -0
- data/examples/website/input/index.md +13 -0
- data/examples/website/input/js/foo.js +1 -0
- data/examples/website/input/js/lib.js +5 -0
- data/examples/website/layouts/default.html +15 -0
- data/examples/website/layouts/subpage.html +5 -0
- data/examples/website/laze.yml +1 -0
- data/features/create_sites.feature +73 -0
- data/features/plugins.feature +49 -0
- data/features/site_data.feature +26 -0
- data/features/step_definitions/laze_steps.rb +77 -0
- data/features/support/env.rb +17 -0
- data/lib/laze.rb +64 -0
- data/lib/laze/asset.rb +7 -0
- data/lib/laze/core_extensions.rb +15 -0
- data/lib/laze/item.rb +59 -0
- data/lib/laze/javascript.rb +5 -0
- data/lib/laze/layout.rb +105 -0
- data/lib/laze/page.rb +33 -0
- data/lib/laze/plugins.rb +56 -0
- data/lib/laze/plugins/cache_buster.rb +49 -0
- data/lib/laze/plugins/css_imports.rb +42 -0
- data/lib/laze/plugins/cssmin.rb +31 -0
- data/lib/laze/plugins/image_check.rb +28 -0
- data/lib/laze/plugins/image_optimizer.rb +52 -0
- data/lib/laze/plugins/js_requires.rb +63 -0
- data/lib/laze/plugins/jsmin.rb +31 -0
- data/lib/laze/plugins/less.rb +35 -0
- data/lib/laze/plugins/robots.rb +28 -0
- data/lib/laze/plugins/sitemap.rb +63 -0
- data/lib/laze/renderer.rb +47 -0
- data/lib/laze/renderers/javascript_renderer.rb +16 -0
- data/lib/laze/renderers/page_renderer.rb +40 -0
- data/lib/laze/renderers/stylesheet_renderer.rb +16 -0
- data/lib/laze/secretary.rb +74 -0
- data/lib/laze/section.rb +39 -0
- data/lib/laze/store.rb +51 -0
- data/lib/laze/stores/filesystem.rb +127 -0
- data/lib/laze/stylesheet.rb +6 -0
- data/lib/laze/target.rb +56 -0
- data/lib/laze/targets/filesystem.rb +41 -0
- data/test/helper.rb +12 -0
- data/test/test_assets.rb +28 -0
- data/test/test_core_extensions.rb +19 -0
- data/test/test_item.rb +59 -0
- data/test/test_layout.rb +47 -0
- data/test/test_renderer.rb +71 -0
- data/test/test_renderers.rb +40 -0
- data/test/test_secretary.rb +48 -0
- data/test/test_store.rb +18 -0
- data/test/test_target.rb +84 -0
- metadata +207 -0
data/lib/laze/layout.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Laze
|
2
|
+
# A layout is a special kind of file that can be used to remove duplication
|
3
|
+
# of HTML boilerplate code. Store a header and footer of a page in a layout
|
4
|
+
# and simply wrap that around a given piece of content to create a page.
|
5
|
+
#
|
6
|
+
# == Inserting content into a layout
|
7
|
+
#
|
8
|
+
# A layout can contain anything you like. The only rule is you should insert
|
9
|
+
# an insertion point for the content of a page. Here's a simple example:
|
10
|
+
#
|
11
|
+
# <html>
|
12
|
+
# <head>
|
13
|
+
# <title>My awesome website</title>
|
14
|
+
# </head>
|
15
|
+
# <body>
|
16
|
+
# {{ yield }}
|
17
|
+
# </body>
|
18
|
+
# </html>
|
19
|
+
#
|
20
|
+
# When your page's content is "Welcome" your resulting page output would be:
|
21
|
+
#
|
22
|
+
# <html>
|
23
|
+
# <head>
|
24
|
+
# <title>My awesome website</title>
|
25
|
+
# </head>
|
26
|
+
# <body>
|
27
|
+
# Welcome
|
28
|
+
# </body>
|
29
|
+
# </html>
|
30
|
+
#
|
31
|
+
# Laze does its best to preserve whitespace in your content.
|
32
|
+
#
|
33
|
+
# == Nested templates
|
34
|
+
#
|
35
|
+
# You can nest multiple layouts, i.e. a layout can itself have a layout.
|
36
|
+
# This way you can create a master template for your site, with
|
37
|
+
# subtemplates for various page types.
|
38
|
+
#
|
39
|
+
# Simply give your layout file a layout like so:
|
40
|
+
#
|
41
|
+
# layout: my_layout
|
42
|
+
# ---
|
43
|
+
# <html>
|
44
|
+
# <head>
|
45
|
+
# <title>My awesome website</title>
|
46
|
+
# </head>
|
47
|
+
# <body>
|
48
|
+
# {{ yield }}
|
49
|
+
# </body>
|
50
|
+
# </html>
|
51
|
+
#
|
52
|
+
# == Template data in layout files
|
53
|
+
#
|
54
|
+
# You can use any variables defined in your pages in your layouts, and
|
55
|
+
# the other way around. You could, for example, include the page title in
|
56
|
+
# your <tt><title></tt> element:
|
57
|
+
#
|
58
|
+
# <html>
|
59
|
+
# <head>
|
60
|
+
# <title>{{ page.title }} - My awesome website</title>
|
61
|
+
# </head>
|
62
|
+
# <body>
|
63
|
+
# {{ yield }}
|
64
|
+
# </body>
|
65
|
+
# </html>
|
66
|
+
#
|
67
|
+
class Layout < Item
|
68
|
+
|
69
|
+
# Regex matching where to insert a layout's content
|
70
|
+
YIELD = /\{\{\s*yield\s*\}\}/
|
71
|
+
|
72
|
+
# Special regex matching an insertion point on a single blank line. In
|
73
|
+
# this case we can preserve whitespace indents.
|
74
|
+
SINGLE_LINE_YIELD = /^(\s+)#{YIELD}\s*$/
|
75
|
+
|
76
|
+
# Apply this layout to a piece of text, returning this layout's content
|
77
|
+
# with the +YIELD+ replacd with the given string.
|
78
|
+
#
|
79
|
+
# If the +YIELD+ is on a single line that is matched by +SINGLE_LINE_YIELD+
|
80
|
+
# it will prefix every line in +string+ with the whitespace indent of the
|
81
|
+
# +YIELD+.
|
82
|
+
def wrap(string)
|
83
|
+
if content =~ SINGLE_LINE_YIELD
|
84
|
+
whitespace = $1
|
85
|
+
content.sub(SINGLE_LINE_YIELD, string.split(/\n/).map { |l| whitespace + l }.join("\n"))
|
86
|
+
else
|
87
|
+
content.sub(YIELD, string)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return this layout's own layout, to enable nested layouts. Simple
|
92
|
+
# shortcut function.
|
93
|
+
def layout
|
94
|
+
properties[:layout]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Use the currently active data store to look for a layout by a given
|
98
|
+
# name. Returns +nil+ when given +nil+, and a new layout instance
|
99
|
+
# otherwise.
|
100
|
+
def self.find(layout_name)
|
101
|
+
return unless layout_name
|
102
|
+
Secretary.current.store.find_layout(layout_name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/laze/page.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Laze
|
2
|
+
# A special kind of Item aimed at HTML files (could be files ending in
|
3
|
+
# html, htm, md, mkd, mdn, markdown or liquid).
|
4
|
+
class Page < Item
|
5
|
+
# Convert this page's content to HTML using a text filter. You can set
|
6
|
+
# the text filter to use with a property +text_filter+.
|
7
|
+
#
|
8
|
+
# Current filters supported are markdown, textile and none. Default is
|
9
|
+
# markdown.
|
10
|
+
def filtered_content
|
11
|
+
text_filter ? text_filter.new(content).to_html : content
|
12
|
+
end
|
13
|
+
|
14
|
+
def filename #:nodoc:
|
15
|
+
@filename ||= properties[:filename].sub(/(?:md|mkd|liquid|markdown|htm)$/, 'html')
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Try to read the text filter to use from the properties. Return nil when
|
21
|
+
# 'none' is defined, RDiscount when undefined or otherwise the
|
22
|
+
# associated filter.
|
23
|
+
def text_filter
|
24
|
+
case properties[:filter]
|
25
|
+
when 'markdown': RDiscount
|
26
|
+
when 'textile': RedCloth
|
27
|
+
when 'none': nil
|
28
|
+
else
|
29
|
+
RDiscount
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/laze/plugins.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Laze
|
2
|
+
# Plugins provides access to all Laze plugins.
|
3
|
+
#
|
4
|
+
# A plugin is a simple decorator that can wrap an Item object. Every plugin
|
5
|
+
# itself can tell you to what objects it applies, and an Item includes
|
6
|
+
# all available plugins by using the +include_plugins+ macro:
|
7
|
+
#
|
8
|
+
# class MyItem < Item
|
9
|
+
# include_plugins :my_item
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# == Example
|
13
|
+
#
|
14
|
+
# A plugin is a simple module that usually replaces methods on Item objects.
|
15
|
+
# Here's a simple example:
|
16
|
+
#
|
17
|
+
# class MyItem < Item
|
18
|
+
# include_plugins :my_item
|
19
|
+
# def foo
|
20
|
+
# 'bar'
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# module Plugins
|
25
|
+
# module MyPlugin
|
26
|
+
# def applies_to?(kind)
|
27
|
+
# kind == :my_item
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def foo
|
31
|
+
# super + '!'
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# MyItem.new.foo # => 'bar!'
|
37
|
+
#
|
38
|
+
# == Working with plugins
|
39
|
+
#
|
40
|
+
# Usually, each plugin should have its own file, require any third-party
|
41
|
+
# libraries and be responsible for when things go wrong.
|
42
|
+
#
|
43
|
+
# A plugin should be stored in /plugins, where it is automatically loaded.
|
44
|
+
module Plugins
|
45
|
+
# Loop over all available plugins, yielding each.
|
46
|
+
def self.each(for_kind = nil) # :yields: module
|
47
|
+
constants.each do |c|
|
48
|
+
const = Laze::Plugins.const_get(c)
|
49
|
+
yield const if const.is_a?(Module) && (for_kind.nil? || const.applies_to?(for_kind))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Load all plugins from /plugins
|
56
|
+
Dir[File.join(File.dirname(__FILE__), 'plugins', '*.rb')].each { |f| require f }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin applies cache busters to CSS files, Javascript files and
|
4
|
+
# image files in your stylesheets. This means it will append the last
|
5
|
+
# modified time of these referenced files to the URL references as
|
6
|
+
# query parameters, so you force the server to bypass caches and give
|
7
|
+
# you a new file when a file has changed.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# # css file
|
12
|
+
# body {
|
13
|
+
# background: url(image.png);
|
14
|
+
# }
|
15
|
+
#
|
16
|
+
# # HTML file
|
17
|
+
# <link href="screen.css">
|
18
|
+
#
|
19
|
+
# Becomes:
|
20
|
+
#
|
21
|
+
# # css file
|
22
|
+
# body {
|
23
|
+
# background: url(image.png?201009231441);
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# # HTML file
|
27
|
+
# <link href="screen.css?201009231441">
|
28
|
+
#
|
29
|
+
module CacheBuster
|
30
|
+
def self.applies_to?(kind) #:nodoc:
|
31
|
+
kind == 'Laze::Renderers::PageRenderer' || kind == 'Laze::Renderers::StylesheetRenderer'
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(locals = {})
|
35
|
+
Laze.info("Adding cache busters to #{options[:locals][:filename]}")
|
36
|
+
[ /((?:href|src)=(?:'|"))(.+\.(?:css|js))("|')/,
|
37
|
+
/(url\([\s"']*)([^\)"'\s]*\.(?:png|gif|jpg))([\s"']*\))/m
|
38
|
+
].inject(super) do |output, regex|
|
39
|
+
output.gsub(regex) do |match|
|
40
|
+
filename = File.expand_path(File.join('input', options[:locals][:path], $2))
|
41
|
+
output = "#{$1}#{$2}%s#{$3}"
|
42
|
+
cache_buster = File.exists?(filename) ? '?' + File.mtime(filename).strftime('%Y%m%d%H%M') : ''
|
43
|
+
output % cache_buster
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin will replace any import statements in your stylesheets with
|
4
|
+
# the actual contents of the referenced files. This reduces the number
|
5
|
+
# of HTTP requests and speeds up your website loading time.
|
6
|
+
#
|
7
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
8
|
+
module CssImports
|
9
|
+
def self.applies_to?(kind) #:nodoc:
|
10
|
+
kind == :target
|
11
|
+
end
|
12
|
+
|
13
|
+
def save
|
14
|
+
expand_css_imports
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def expand_css(content, filename)
|
21
|
+
Laze.info("Expanding imports in #{filename}")
|
22
|
+
content.gsub!(/@import\s*url\(\s*('|")?\s*([^'"]+)\s*\1?\s*\);?/) do |match|
|
23
|
+
referenced_file = File.expand_path(File.join(File.dirname(filename), $2))
|
24
|
+
if File.exists?(referenced_file)
|
25
|
+
"/* #{match} */\n" + expand_css(File.read(referenced_file), referenced_file)
|
26
|
+
else
|
27
|
+
Laze.warn "CSS Imported file not found: #{referenced_file}"
|
28
|
+
match
|
29
|
+
end
|
30
|
+
end
|
31
|
+
content
|
32
|
+
end
|
33
|
+
|
34
|
+
def expand_css_imports
|
35
|
+
Dir[File.join(output_dir, '**', '*.css')].each do |filename|
|
36
|
+
content = File.read(filename)
|
37
|
+
File.open(filename, 'w') { |f| f.write expand_css(content, filename) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'cssmin'
|
3
|
+
module Laze #:nodoc:
|
4
|
+
module Plugins #:nodoc:
|
5
|
+
# This plugin minifies the output of your CSS files, reducing your
|
6
|
+
# website's bandwidth usage.
|
7
|
+
#
|
8
|
+
# This plugin is a decorator for StylesheetRenderer and fires after
|
9
|
+
# StylesheetRenderer#render.
|
10
|
+
#
|
11
|
+
# *Note*: when the cssmin gem is unavailable this plugin will quietly
|
12
|
+
# fail, leaving the .css files untouched and generating a warning in the
|
13
|
+
# logs.
|
14
|
+
module Cssmin
|
15
|
+
def self.applies_to?(kind) #:nodoc:
|
16
|
+
kind == 'Laze::Renderers::StylesheetRenderer'
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(locals = {})
|
20
|
+
return super unless Secretary.current.options[:minify_css]
|
21
|
+
@minified_content = begin
|
22
|
+
Laze.info "Minifying #{options[:locals][:filename]}"
|
23
|
+
::CSSMin.minify(super)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
Laze.warn 'The cssmin gem is required to minify .css files.'
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin checks your stylesheets for missing images and spits out
|
4
|
+
# a log warning message when an image file referenced in your
|
5
|
+
# stylesheets could not be found.
|
6
|
+
#
|
7
|
+
# This plugin is a decorator for StylesheetRenderer and fires after
|
8
|
+
# StylesheetRenderer#render.
|
9
|
+
module ImageCheck
|
10
|
+
def self.applies_to?(kind) #:nodoc:
|
11
|
+
kind == 'Laze::Renderers::StylesheetRenderer'
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(locals = {})
|
15
|
+
Laze.debug 'Checking for missing stylesheet images'
|
16
|
+
super.scan(/url\(\s*('|")?\s*(.+?)\s*\1?\s*\)/) do |match|
|
17
|
+
filename = match[1].split("?", 2).first
|
18
|
+
if %w(.gif .png .jpg .jpeg).include?(File.extname(filename))
|
19
|
+
path = File.expand_path(File.join(Secretary.current.options[:source], 'input', options[:locals][:path], filename))
|
20
|
+
unless File.exists?(path)
|
21
|
+
Laze.warn "Image #{path} not found (#{options[:locals][:filename]})."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin tries to optimize the images in your website.
|
4
|
+
# You will need to have +pngcrush+ and +jpegtran+ installed on your
|
5
|
+
# system for this plugin to work. If not, it will fail silently.
|
6
|
+
#
|
7
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
8
|
+
module ImageOptimizer
|
9
|
+
def self.applies_to?(kind) #:nodoc:
|
10
|
+
kind == :target
|
11
|
+
end
|
12
|
+
|
13
|
+
def save
|
14
|
+
optimize_images
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def optimize_images
|
21
|
+
pngcrush = `which pngcrush`
|
22
|
+
jpegtran = `which jpegtran`
|
23
|
+
|
24
|
+
Laze.warn 'pngcrush not found; skipping PNG optimization' if pngcrush.empty?
|
25
|
+
Laze.warn 'jpegtran not found; skipping JPG optimization' if jpegtran.empty?
|
26
|
+
|
27
|
+
Dir[File.join(output_dir, '**/*.{png,jpg}')].each do |filename|
|
28
|
+
temp_file = filename + '_optimized'
|
29
|
+
if File.extname(filename).downcase == '.png' && !pngcrush.empty?
|
30
|
+
optimize_png(filename, temp_file)
|
31
|
+
elsif File.extname(filename).downcase == '.jpg' && !jpegtran.empty?
|
32
|
+
optimize_jpg(filename, temp_file)
|
33
|
+
end
|
34
|
+
if File.exists?(temp_file)
|
35
|
+
FileUtils.rm(filename)
|
36
|
+
FileUtils.mv(temp_file, filename)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def optimize_png(filename, temp_file)
|
42
|
+
Laze.info "Optimizing #{filename}"
|
43
|
+
system("pngcrush -q -reduce -brute #{filename} #{temp_file}")
|
44
|
+
end
|
45
|
+
|
46
|
+
def optimize_jpg(filename, temp_file)
|
47
|
+
Laze.info "Optimizing #{filename}"
|
48
|
+
system("jpegtran -copy none -progressive -outfile #{temp_file} #{filename}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin introduces the +require+ statement to your javascript files.
|
4
|
+
# This is a simple mechanism to concatenate all your javascript files
|
5
|
+
# into a single library and thereby reduce the number of HTTP requests
|
6
|
+
# (improving your website load time).
|
7
|
+
#
|
8
|
+
# You can include another javascript file with a special comment like so:
|
9
|
+
#
|
10
|
+
# # foo.js
|
11
|
+
# var foo = 'foo';
|
12
|
+
#
|
13
|
+
# # bar.js
|
14
|
+
# // require 'foo.js'
|
15
|
+
# alert(foo);
|
16
|
+
#
|
17
|
+
# This will compile into the following file:
|
18
|
+
#
|
19
|
+
# # bar.js
|
20
|
+
# var foo = 'foo';
|
21
|
+
# alert(foo);
|
22
|
+
#
|
23
|
+
# Note that this leaves the original files untouched, so your website
|
24
|
+
# will end up with both files. The point is you only have to reference
|
25
|
+
# the one with that is concatenated.
|
26
|
+
#
|
27
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
28
|
+
module JsRequires
|
29
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
30
|
+
def self.applies_to?(kind) #:nodoc:
|
31
|
+
kind == :target
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
expand_js_requires
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def expand_js(content, filename)
|
42
|
+
Laze.info("Expanding requires in #{filename}")
|
43
|
+
content.gsub!(/^\s*\/\/\s*requires? ('|")?(.+)\1$/) do |match|
|
44
|
+
referenced_file = File.expand_path(File.join(File.dirname(filename), $2))
|
45
|
+
if File.exists?(referenced_file)
|
46
|
+
"#{match}\n" + expand_js(File.read(referenced_file), referenced_file)
|
47
|
+
else
|
48
|
+
Laze.warn "JS required file not found: #{referenced_file}"
|
49
|
+
match
|
50
|
+
end
|
51
|
+
end
|
52
|
+
content
|
53
|
+
end
|
54
|
+
|
55
|
+
def expand_js_requires
|
56
|
+
Dir[File.join(output_dir, '**', '*.js')].each do |filename|
|
57
|
+
content = File.read(filename)
|
58
|
+
File.open(filename, 'w') { |f| f.write expand_js(content, filename) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|