laze 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|