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
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'jsmin'
|
3
|
+
module Laze #:nodoc:
|
4
|
+
module Plugins #:nodoc:
|
5
|
+
# This plugin uses the jsmin gem to minify your javascript files
|
6
|
+
# and thereby reduce your website's bandwidth usage.
|
7
|
+
#
|
8
|
+
# This plugin is a decorator for JavascriptRenderer and fires after
|
9
|
+
# JavascriptRenderer#render.
|
10
|
+
#
|
11
|
+
# *Note*: when the jsmin gem is unavailable this plugin will quietly
|
12
|
+
# fail, leaving the .js files untouched and generating a warning in the
|
13
|
+
# logs.
|
14
|
+
module Jsmin
|
15
|
+
def self.applies_to?(kind) #:nodoc:
|
16
|
+
kind == 'Laze::Renderers::JavascriptRenderer'
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(locals = {})
|
20
|
+
return super unless Secretary.current.options[:minify_js]
|
21
|
+
@minified_content = begin
|
22
|
+
Laze.info "Minifying #{options[:locals][:filename]}"
|
23
|
+
::JSMin.minify(super)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
Laze.warn 'The jsmin gem is required to minify .js files.'
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
begin
|
2
|
+
require 'less'
|
3
|
+
module Laze #:nodoc:
|
4
|
+
module Plugins #:nodoc:
|
5
|
+
# This plugin parses stylesheets written in Less to normal CSS files.
|
6
|
+
# The filename extension is converted to end in .css.
|
7
|
+
#
|
8
|
+
# See http://lesscss.org for more information.
|
9
|
+
#
|
10
|
+
# *Note*: when the less gem is unavailable this plugin will quietly fail,
|
11
|
+
# leaving the .less files untouched and generating a warning in the
|
12
|
+
# logs.
|
13
|
+
module Less
|
14
|
+
def self.applies_to?(kind) #:nodoc:
|
15
|
+
kind == :stylesheet
|
16
|
+
end
|
17
|
+
|
18
|
+
# Parse the contents of the file with Less if the original filename
|
19
|
+
# ends with <tt>.less</tt>
|
20
|
+
def content
|
21
|
+
return super unless properties[:filename] =~ /\.less$/
|
22
|
+
@less_content ||= ::Less.parse(super)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Overrides a stylesheet's filename to replace .less with .css, so
|
26
|
+
# that <tt>screen.less</tt> becomes <tt>screen.css</tt>
|
27
|
+
def filename
|
28
|
+
super.sub(/\.less$/, '.css')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
Laze.warn 'The Less gem is required to convert .less files.'
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Laze #:nodoc:
|
2
|
+
module Plugins #:nodoc:
|
3
|
+
# This plugin creates a very simple robots.txt file with a reference
|
4
|
+
# to the sitemap.
|
5
|
+
#--
|
6
|
+
# TODO: check to make sure there is not already a robots.txt
|
7
|
+
# TODO: auto-build the file based on properties in the pages.
|
8
|
+
module Robots
|
9
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
10
|
+
def self.applies_to?(kind) #:nodoc:
|
11
|
+
kind == :target
|
12
|
+
end
|
13
|
+
|
14
|
+
def save
|
15
|
+
create_robots_txt
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_robots_txt
|
22
|
+
File.open(File.join(output_dir, 'robots.txt'), 'w') do |f|
|
23
|
+
f.write 'Sitemap: sitemap.xml'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
begin
|
2
|
+
require 'builder'
|
3
|
+
require 'zlib'
|
4
|
+
module Laze #:nodoc:
|
5
|
+
module Plugins #:nodoc:
|
6
|
+
# This plugin will generate a sitemap for your website after it has been
|
7
|
+
# generated. It will scan for all HTML files in your output directory
|
8
|
+
# and will collect these in a sitemap.xml file.
|
9
|
+
#
|
10
|
+
# This plugin will also create a gzipped sitemap in sitemap.xml.gz.
|
11
|
+
#
|
12
|
+
# This plugin is a decorator for Target and fires before Target#save.
|
13
|
+
#
|
14
|
+
# *Note*: when the builder or zlib gems are unavailable this plugin will
|
15
|
+
# quietly fail, generating no sitemap files and generating a warning in
|
16
|
+
# the logs.
|
17
|
+
module Sitemap
|
18
|
+
def self.applies_to?(kind) #:nodoc:
|
19
|
+
kind == :target
|
20
|
+
end
|
21
|
+
|
22
|
+
def save
|
23
|
+
create_and_deflate_sitemap(generate_sitemap)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_and_deflate_sitemap(sitemap)
|
30
|
+
path = File.join(output_dir, 'sitemap.xml')
|
31
|
+
Laze.debug 'Writing sitemap.xml'
|
32
|
+
File.open(path, 'w') do |f|
|
33
|
+
f.write sitemap
|
34
|
+
end
|
35
|
+
Laze.debug 'Writing sitemap.xml.gz'
|
36
|
+
Zlib::GzipWriter.open(path + '.gz') do |gz|
|
37
|
+
gz.write sitemap
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_sitemap
|
42
|
+
Laze.info 'Generating XML sitemap'
|
43
|
+
sitemap = ''
|
44
|
+
xml = Builder::XmlMarkup.new(:target => sitemap, :indent => 2)
|
45
|
+
xml.instruct!
|
46
|
+
xml.urlset(:xmlns=>'http://www.sitemaps.org/schemas/sitemap/0.9') do
|
47
|
+
Dir[File.join(output_dir, '**/*.html')].each do |page|
|
48
|
+
Laze.debug "Including #{page}"
|
49
|
+
xml.url do
|
50
|
+
xml.loc(page.sub(output_dir, (Secretary.current.options[:domain]) || ''))
|
51
|
+
xml.lastmod(File.mtime(page).strftime("%Y-%m-%dT%H:%M:%S+00:00"))
|
52
|
+
xml.changefreq('weekly')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
sitemap
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
rescue LoadError
|
62
|
+
Laze.warn 'The builder and zlib gems are needed to generate sitemaps'
|
63
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Laze
|
2
|
+
# The renderer takes an item and creates output to write to the target.
|
3
|
+
# This is a generic rendering class, which will dispatch various Item's
|
4
|
+
# to specialised subclasses, like PageRenderer or StylesheetRenderer.
|
5
|
+
#
|
6
|
+
# This class's +render+ method will commonly be used to create and call a
|
7
|
+
# new renderer.
|
8
|
+
class Renderer
|
9
|
+
|
10
|
+
# Options for rendering. The +:locals+ key will also contain any
|
11
|
+
# variables to be available in the liquid templates.
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# The string contents to render
|
15
|
+
attr_reader :string
|
16
|
+
|
17
|
+
def initialize(item_or_string, options = nil) #:nodoc:
|
18
|
+
raise ArgumentError, 'Please provide an item' unless item_or_string.kind_of?(Item) || (item_or_string.is_a?(String) && options.is_a?(Hash))
|
19
|
+
if item_or_string.is_a?(Item)
|
20
|
+
@string = item_or_string.content
|
21
|
+
@options = { :locals => item_or_string.properties }
|
22
|
+
else
|
23
|
+
@string = item_or_string
|
24
|
+
@options = { :locals => options }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add plugins
|
28
|
+
Plugins.each(self.class.to_s) { |p| extend p }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Convert the item to a string to be output to the deployment target.
|
32
|
+
def render
|
33
|
+
raise 'This is a generic store. Please use a subclass.'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Shortcut method to <tt>new(...).render</tt>
|
37
|
+
# This will automatically select the right subclass to use for the
|
38
|
+
# incoming item.
|
39
|
+
def self.render(item, locals = {})
|
40
|
+
case item
|
41
|
+
when Page: Renderers::PageRenderer
|
42
|
+
when Stylesheet: Renderers::StylesheetRenderer
|
43
|
+
when Javascript: Renderers::JavascriptRenderer
|
44
|
+
end.new(item).render(locals)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Laze
|
2
|
+
module Renderers #:nodoc:
|
3
|
+
# Render javascript source files to output files. Does nothing right now,
|
4
|
+
# just output the javascript directly.
|
5
|
+
class JavascriptRenderer < Renderer
|
6
|
+
def initialize(page) #:nodoc:
|
7
|
+
raise ArgumentError unless page.is_a?(Javascript)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(locals = {})
|
12
|
+
string
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Laze
|
2
|
+
module Renderers #:nodoc:
|
3
|
+
# Renders Page objects to an HTML page. This means applying text filters,
|
4
|
+
# layouts and the Liquid templating engine to a source file and returning
|
5
|
+
# the full HTML result file.
|
6
|
+
class PageRenderer < Renderer
|
7
|
+
def initialize(page) #:nodoc:
|
8
|
+
raise ArgumentError unless page.is_a?(Page)
|
9
|
+
super(page.filtered_content, page.properties)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Apply layouts and the liquid templating language.
|
13
|
+
def render(locals = {})
|
14
|
+
liquify(wrap_in_layout(string), (options[:locals] || {}).merge(locals))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def wrap_in_layout(content_to_wrap)
|
20
|
+
# do nothing if there is no layout option
|
21
|
+
return content_to_wrap unless options[:locals] && options[:locals][:layout]
|
22
|
+
|
23
|
+
# do nothing if there is a layout option, but it can't be found
|
24
|
+
return content_to_wrap unless layout = Layout.find(options[:locals][:layout])
|
25
|
+
|
26
|
+
# Recursively wrap string in layout
|
27
|
+
output = content_to_wrap
|
28
|
+
while layout
|
29
|
+
output = layout.wrap(output)
|
30
|
+
layout = Layout.find(layout.layout)
|
31
|
+
end
|
32
|
+
output
|
33
|
+
end
|
34
|
+
|
35
|
+
def liquify(string, locals = {})
|
36
|
+
Liquid::Template.parse(string).render('page' => locals.stringify_keys)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Laze
|
2
|
+
module Renderers #:nodoc:
|
3
|
+
# Render CSS source files to output files. Does nothing right now,
|
4
|
+
# just output the CSS directly.
|
5
|
+
class StylesheetRenderer < Renderer
|
6
|
+
def initialize(page) #:nodoc:
|
7
|
+
raise ArgumentError unless page.is_a?(Stylesheet)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(locals = {})
|
12
|
+
string
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Laze
|
2
|
+
# The Secretary combines all other classes to a working application and
|
3
|
+
# serves as a central point of information for other classes that need to
|
4
|
+
# know stuff that are none of their primary concern.
|
5
|
+
#
|
6
|
+
# The secretary keeps track of the build options and the storage and
|
7
|
+
# target engines that are currently used.
|
8
|
+
class Secretary
|
9
|
+
|
10
|
+
# Options that tell Laze what to do and how to do it.
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Since there should only be a singe secretary object, we let the
|
15
|
+
# singleton class keep track of the current instance.
|
16
|
+
attr_accessor :current
|
17
|
+
|
18
|
+
# Read configuration from the laze.yml file in the current directory.
|
19
|
+
#--
|
20
|
+
# TODO: make sure this reads from the source directory, not the current.
|
21
|
+
def site_config
|
22
|
+
if File.exists?('laze.yml')
|
23
|
+
YAML.load_file('laze.yml').symbolize_keys
|
24
|
+
else
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(options = {}) #:nodoc:
|
31
|
+
default_options = {
|
32
|
+
:store => :filesystem,
|
33
|
+
:target => :filesystem,
|
34
|
+
:source => '.',
|
35
|
+
:directory => 'output',
|
36
|
+
:minify_js => false,
|
37
|
+
:minify_css => false,
|
38
|
+
:loglevel => :warn,
|
39
|
+
:logfile => 'stderr'
|
40
|
+
}
|
41
|
+
@options = default_options.merge(self.class.site_config).merge(options)
|
42
|
+
|
43
|
+
# Set the logger options
|
44
|
+
logger = Logger.new((@options[:logfile] == 'stderr' ? STDERR : @options[:logfile]))
|
45
|
+
logger.level = Logger.const_get(@options[:loglevel].to_s.upcase)
|
46
|
+
logger.datetime_format = "%H:%M:%S"
|
47
|
+
Laze.const_set('LOGGER', logger) unless Laze.const_defined?('LOGGER')
|
48
|
+
|
49
|
+
Secretary.current = self
|
50
|
+
end
|
51
|
+
|
52
|
+
# The current storage engine.
|
53
|
+
def store
|
54
|
+
@store ||= Store.find(options[:store]).new(options[:source])
|
55
|
+
end
|
56
|
+
|
57
|
+
# The current target deployment engine.
|
58
|
+
def target
|
59
|
+
@target ||= Target.find(options[:target]).new(options[:directory])
|
60
|
+
end
|
61
|
+
|
62
|
+
# Run laze to build the output website.
|
63
|
+
def run
|
64
|
+
Laze.debug 'Starting source processing'
|
65
|
+
target.reset
|
66
|
+
store.each do |item|
|
67
|
+
target.create item
|
68
|
+
end
|
69
|
+
target.save
|
70
|
+
Laze.debug 'Source processing ready'
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
data/lib/laze/section.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Laze
|
2
|
+
# A section is a special kind of Item that maps to a filesystem directory.
|
3
|
+
# You can move it around like an item, but it is actually a container object
|
4
|
+
# for its subitems.
|
5
|
+
#
|
6
|
+
# A Section's subitems can be accessed as an Enumerable.
|
7
|
+
class Section < Item
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(properties) #:nodoc:
|
11
|
+
super(properties, nil)
|
12
|
+
@items = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
@items.each { |item| yield item }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add a new subitem to this section.
|
20
|
+
def add_item(item)
|
21
|
+
@items << item
|
22
|
+
item.parent = self
|
23
|
+
end
|
24
|
+
alias_method :<<, :add_item
|
25
|
+
|
26
|
+
# Remove the given subitem from this section.
|
27
|
+
def remove_item(item)
|
28
|
+
@items.delete(item)
|
29
|
+
item.parent = nil
|
30
|
+
self
|
31
|
+
end
|
32
|
+
alias_method :delete, :remove_item
|
33
|
+
|
34
|
+
# Count the number of subitems (and their subitems)
|
35
|
+
def number_of_subitems
|
36
|
+
@items.inject(0) { |total, item| total += 1 + item.number_of_subitems }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/laze/store.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Laze
|
2
|
+
class Store
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# Generic exception to be raised for store-specific exceptions.
|
6
|
+
StoreException = Class.new(Exception)
|
7
|
+
|
8
|
+
# Exception for when interacting with the filesystem goes bad.
|
9
|
+
FileSystemException = Class.new(StoreException)
|
10
|
+
|
11
|
+
@stores = []
|
12
|
+
|
13
|
+
# Find a storage engine by name and return its class.
|
14
|
+
#
|
15
|
+
# When loading <tt>Laze::Stores::Filesystem</tt> you would call:
|
16
|
+
#
|
17
|
+
# Store.find(:filesystem)
|
18
|
+
#
|
19
|
+
def self.find(kind)
|
20
|
+
stores = @stores.select { |s| s.name.to_s.split('::').last.downcase.to_sym == kind }
|
21
|
+
raise StoreException, 'No such store.' unless stores.any?
|
22
|
+
stores.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.inherited(child) #:nodoc:
|
26
|
+
@stores << child
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize #:nodoc:
|
30
|
+
Liquid::Template.file_system = self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Loop over all the items in the current project and yield them
|
34
|
+
# to the block.
|
35
|
+
def each
|
36
|
+
raise 'This is a generic store. Please use a subclass.'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return a new instance of Layout by finding a layout by the given name
|
40
|
+
# in the current project.
|
41
|
+
def find_layout
|
42
|
+
raise 'This is a generic store. Please use a subclass.'
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the contents of any project file. This method is also used
|
46
|
+
# by the Liquid templating engine to read includes.
|
47
|
+
def read_template_file
|
48
|
+
raise 'This is a generic store. Please use a subclass.'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|