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,127 @@
|
|
1
|
+
module Laze
|
2
|
+
module Stores #:nodoc:
|
3
|
+
class Filesystem < Store
|
4
|
+
# Path from the project root to where includes are stored.
|
5
|
+
INCLUDES_DIR = 'includes'
|
6
|
+
|
7
|
+
# Path from the project root to where layouts are stored.
|
8
|
+
LAYOUTS_DIR = 'layouts'
|
9
|
+
|
10
|
+
# Path from the project root to where all input files are stored.
|
11
|
+
INPUT_DIR = 'input'
|
12
|
+
|
13
|
+
# Create a new filesystem object for a given project root directory,
|
14
|
+
# which defaults to the current working directory.
|
15
|
+
def initialize(root = Dir.pwd)
|
16
|
+
@root = root
|
17
|
+
super()
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
scan_directory(File.join(@root, INPUT_DIR), &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_layout(layout_name)
|
25
|
+
FileWithMetadata.new(read_template_file(layout_name, LAYOUTS_DIR)).to_layout
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_template_file(include_name, from_dir = INCLUDES_DIR)
|
29
|
+
Laze.debug "Reading template file #{include_name}"
|
30
|
+
raise FileSystemException, "Illegal filename '#{include_name}'" unless include_name =~ /^[^.\/][a-zA-Z0-9_\/]+$/
|
31
|
+
full_path = File.join(@root, from_dir, "#{include_name}.html")
|
32
|
+
raise FileSystemException, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(@root)}/
|
33
|
+
File.read(full_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def scan_directory(path)
|
39
|
+
Laze.debug "Recursing into #{path}"
|
40
|
+
|
41
|
+
Dir.foreach(path) do |filename|
|
42
|
+
# Skip directories
|
43
|
+
next if %w[. ..].include?(filename)
|
44
|
+
|
45
|
+
# Get current entry path
|
46
|
+
full_path = File.join(path, filename)
|
47
|
+
|
48
|
+
if File.file?(full_path)
|
49
|
+
relative_path = File.dirname(full_path.sub(File.join(@root, 'input/'), ''))
|
50
|
+
file_content = File.read(full_path)
|
51
|
+
file = FileWithMetadata.new(file_content, { :filename => filename, :path => relative_path })
|
52
|
+
yield case File.extname(filename)
|
53
|
+
when /\.(css|less)/: file.to_stylesheet
|
54
|
+
when '.js': file.to_javascript
|
55
|
+
else
|
56
|
+
if file_content =~ /---|\{%.+%\}|\{\{.+\}\}/
|
57
|
+
file.to_page
|
58
|
+
else
|
59
|
+
# just copy over without modification
|
60
|
+
file.to_item
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
elsif File.directory?(full_path)
|
65
|
+
section = Section.new({ :filename => filename })
|
66
|
+
scan_directory(full_path) do |subitem|
|
67
|
+
section << subitem
|
68
|
+
end
|
69
|
+
yield section
|
70
|
+
|
71
|
+
else
|
72
|
+
Laze.debug "Skipping #{path}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class FileWithMetadata #:nodoc:
|
78
|
+
attr_reader :content, :properties
|
79
|
+
|
80
|
+
METADATA_SEPARATOR = /^---$\s*/
|
81
|
+
|
82
|
+
def initialize(file_contents, extra_metadata = {})
|
83
|
+
@file_contents, @properties = file_contents, extra_metadata
|
84
|
+
split
|
85
|
+
end
|
86
|
+
|
87
|
+
def has?(key)
|
88
|
+
properties.has_key?(key)
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_item
|
92
|
+
Item.new(properties, content)
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_page
|
96
|
+
Page.new(properties, content)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_layout
|
100
|
+
Layout.new(properties, content)
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_stylesheet
|
104
|
+
Stylesheet.new(properties, content)
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_javascript
|
108
|
+
Javascript.new(properties, content)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def split
|
114
|
+
@content = @file_contents
|
115
|
+
if @file_contents =~ METADATA_SEPARATOR
|
116
|
+
yaml_string, @content = @file_contents.split(METADATA_SEPARATOR, 2)
|
117
|
+
@properties = yaml_string_to_properties(yaml_string).merge(@properties)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def yaml_string_to_properties(yaml_string)
|
122
|
+
properties = YAML.load(yaml_string).symbolize_keys
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/laze/target.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Laze
|
2
|
+
class Target
|
3
|
+
|
4
|
+
# Generic exception to be raised for store-specific exceptions.
|
5
|
+
TargetException = Class.new(Exception)
|
6
|
+
|
7
|
+
# Exception for when interacting with the filesystem goes bad.
|
8
|
+
FileSystemException = Class.new(TargetException)
|
9
|
+
|
10
|
+
@targets = []
|
11
|
+
|
12
|
+
# The base directory to create all the files in. This is relative
|
13
|
+
# the location where laze is run from.
|
14
|
+
attr_reader :output_dir
|
15
|
+
|
16
|
+
# Find a target deployment engine by name and return its class.
|
17
|
+
#
|
18
|
+
# When loading <tt>Laze::Targets::Filesystem</tt> you would call:
|
19
|
+
#
|
20
|
+
# Target.find(:filesystem)
|
21
|
+
#
|
22
|
+
def self.find(kind)
|
23
|
+
targets = @targets.select { |s| s.name.to_s.split('::').last.downcase.to_sym == kind }
|
24
|
+
raise TargetException, 'No such target.' unless targets.any?
|
25
|
+
targets.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.inherited(child) #:nodoc:
|
29
|
+
@targets << child
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(output_dir) #:nodoc:
|
33
|
+
@output_dir = output_dir
|
34
|
+
reset
|
35
|
+
Laze::Plugins.each(:target) { |plugin| extend plugin }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Finalize the generation and process any after hooks.
|
39
|
+
def save
|
40
|
+
raise 'This is a generic target. Please use a subclass.'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Given an item this will create the output file in the right output
|
44
|
+
# location.
|
45
|
+
def create(item)
|
46
|
+
raise 'This is a generic target. Please use a subclass.'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Empty the current output directory
|
50
|
+
def reset
|
51
|
+
FileUtils.rm_rf(output_dir) if File.directory?(output_dir)
|
52
|
+
FileUtils.mkdir(output_dir) unless File.directory?(output_dir)
|
53
|
+
Laze.debug "Emptied output directory #{output_dir}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Laze
|
2
|
+
module Targets #:nodoc:
|
3
|
+
class Filesystem < Target
|
4
|
+
|
5
|
+
# Manifest an item -- write it to disk
|
6
|
+
def create(item)
|
7
|
+
case item
|
8
|
+
when Page, Asset: create_page(item)
|
9
|
+
when Section: create_section(item)
|
10
|
+
when Item: copy_file(item)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def save
|
15
|
+
# already done...
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def copy_file(item)
|
21
|
+
File.open(File.join(output_dir, item.properties[:path], item.properties[:filename]), 'w') { |f| f.write item.content }
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_page(item)
|
25
|
+
File.open(dir(item), 'w') { |f| f.write Renderer.render(item) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_section(item)
|
29
|
+
FileUtils.mkdir(dir(item))
|
30
|
+
item.each { |subitem| create(subitem) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the correct path for a given item.
|
34
|
+
# This finds all the ancestors for an item, joins them together
|
35
|
+
# as directories and returns the path name.
|
36
|
+
def dir(item)
|
37
|
+
File.join(output_dir, *(item.ancestors.map{ |i| i.filename } << item.filename))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'laze'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
include Laze
|
12
|
+
end
|
data/test/test_assets.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestAssets < Test::Unit::TestCase
|
4
|
+
context "generic asset" do
|
5
|
+
should "take content and options" do
|
6
|
+
asset = Asset.new({ :title => 'foo' }, 'bar')
|
7
|
+
assert_equal({:title => 'foo'}, asset.properties)
|
8
|
+
assert_equal('bar', asset.content)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "return its filename" do
|
12
|
+
asset = Asset.new({:filename => 'foo'}, 'bar')
|
13
|
+
assert_equal('foo', asset.filename)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "stylesheet" do
|
18
|
+
setup do
|
19
|
+
@less_stylesheet = Stylesheet.new({ :filename => 'base.less' }, 'foo')
|
20
|
+
@stylesheet = Stylesheet.new({ :filename => 'base.css' }, 'foo')
|
21
|
+
end
|
22
|
+
|
23
|
+
should "convert the filename to css" do
|
24
|
+
assert_equal('base.css', @less_stylesheet.filename)
|
25
|
+
assert_equal('base.css', @stylesheet.filename)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestCoreExtensions < Test::Unit::TestCase
|
4
|
+
context "hashes" do
|
5
|
+
setup do
|
6
|
+
@a = { 'foo' => 'bar' }
|
7
|
+
@b = { :foo => 'bar' }
|
8
|
+
end
|
9
|
+
|
10
|
+
should "convert keys to strings" do
|
11
|
+
assert_equal(@a, @b.stringify_keys)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "convert keys to symbols" do
|
15
|
+
assert_equal(@b, @a.symbolize_keys)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/test/test_item.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestItemPageAndSection < Test::Unit::TestCase
|
4
|
+
context "with children" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@x = Page.new({ :title => 'x', :filename => 'foo' }, 'page x')
|
8
|
+
@y = Page.new({ :title => 'y' }, 'page y')
|
9
|
+
@z = Page.new({ :title => 'z' }, 'page z')
|
10
|
+
@a = Section.new({ :title => 'a' })
|
11
|
+
@b = Section.new({ :title => 'b' })
|
12
|
+
@a.add_item @x
|
13
|
+
@b.add_item @y
|
14
|
+
@b.add_item @z
|
15
|
+
@a.add_item @b
|
16
|
+
end
|
17
|
+
|
18
|
+
should "tell it has a property" do
|
19
|
+
assert @x.has?(:title)
|
20
|
+
assert !@x.has?(:foo)
|
21
|
+
end
|
22
|
+
|
23
|
+
should "inspect nicely" do
|
24
|
+
assert_match(/#<Laze::Page:0x\w+? foo>/, @x.inspect)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "tell its filename" do
|
28
|
+
assert_equal('foo', @x.filename)
|
29
|
+
assert_equal(@x.filename, @x.properties[:filename])
|
30
|
+
end
|
31
|
+
|
32
|
+
should "convert to string by its filename" do
|
33
|
+
assert_equal('foo', @x.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'have subitems' do
|
37
|
+
assert_equal(4, @a.number_of_subitems)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "remove subitems" do
|
41
|
+
assert_equal(3, @a.remove_item(@x).number_of_subitems)
|
42
|
+
assert_nil(@x.parent)
|
43
|
+
end
|
44
|
+
|
45
|
+
should "enumerate over subitems" do
|
46
|
+
i = 0
|
47
|
+
@b.each do |item|
|
48
|
+
assert_kind_of(Item, item)
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
assert_equal(2, i)
|
52
|
+
end
|
53
|
+
|
54
|
+
should "count the number of ancestors" do
|
55
|
+
assert_equal(2, @y.ancestors.size)
|
56
|
+
assert_equal(0, @a.ancestors.size)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/test/test_layout.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestLayout < Test::Unit::TestCase
|
4
|
+
context "when creating" do
|
5
|
+
should "store content" do
|
6
|
+
assert_equal('foo', Layout.new({}, 'foo').content)
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return nil for a nil layout" do
|
10
|
+
assert_nil(Layout.find(nil))
|
11
|
+
end
|
12
|
+
|
13
|
+
should "ask secretary for a layout" do
|
14
|
+
store = mock()
|
15
|
+
current = mock()
|
16
|
+
store.expects(:find_layout).with('foo').returns('bar')
|
17
|
+
current.expects(:store).returns(store)
|
18
|
+
Secretary.expects(:current).returns(current)
|
19
|
+
assert_equal('bar', Layout.find('foo'))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "simple layout" do
|
24
|
+
setup do
|
25
|
+
@layout = Layout.new({ :layout => 'foo' }, 'bar: {{ yield }}')
|
26
|
+
end
|
27
|
+
|
28
|
+
should "return its layout" do
|
29
|
+
assert_equal('foo', @layout.layout)
|
30
|
+
end
|
31
|
+
|
32
|
+
should "wrap itself around a string" do
|
33
|
+
assert_equal("bar: baz", @layout.wrap('baz'))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "complex layout" do
|
38
|
+
setup do
|
39
|
+
@layout = Layout.new({ :layout => 'foo' }, "bar\n {{ yield }}\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
should "preserve whitespace indent" do
|
43
|
+
assert_equal("bar\n foo", @layout.wrap('foo'))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestRenderer < Test::Unit::TestCase
|
4
|
+
context 'with bad options' do
|
5
|
+
should 'raise an error with no arguments' do
|
6
|
+
assert_raise(ArgumentError) { Renderer.new }
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'raise an error with too many arguments' do
|
10
|
+
assert_raise(ArgumentError) { Renderer.new(0, 1, 2) }
|
11
|
+
end
|
12
|
+
|
13
|
+
should "raise an error when not passed in an item or string" do
|
14
|
+
assert_raise(ArgumentError) { Renderer.new(0) }
|
15
|
+
assert_nothing_raised(ArgumentError) { Renderer.new(Page.new({}, 'foo')) }
|
16
|
+
assert_nothing_raised(ArgumentError) { Renderer.new('foo', {}) }
|
17
|
+
end
|
18
|
+
|
19
|
+
should "raise an error when using the generic class" do
|
20
|
+
assert_raise(RuntimeError) { Renderer.new('foo', {}).render }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when creating' do
|
25
|
+
should 'take a string and options' do
|
26
|
+
renderer = Renderer.new('foo', { :bar => 'bar' })
|
27
|
+
assert_equal('foo', renderer.string)
|
28
|
+
assert_equal({ :locals => { :bar => 'bar' }}, renderer.options)
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'take a page as string and options' do
|
32
|
+
renderer = Renderer.new(Page.new({ :bar => 'bar'}, 'foo'))
|
33
|
+
assert_equal('foo', renderer.string)
|
34
|
+
assert_equal({ :locals => { :bar => 'bar' }}, renderer.options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
should "render a stylesheet" do
|
39
|
+
s = Stylesheet.new({ :filename => 'foo.css' }, 'foo')
|
40
|
+
x = Renderers::StylesheetRenderer.new(s)
|
41
|
+
x.expects(:render).returns('foo')
|
42
|
+
Renderers::StylesheetRenderer.expects(:new).at_least_once.returns(x)
|
43
|
+
Renderer.render(s)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "render a javascript" do
|
47
|
+
s = Javascript.new({ :filename => 'foo.js' }, 'foo')
|
48
|
+
x = Renderers::JavascriptRenderer.new(s)
|
49
|
+
x.expects(:render).returns('foo')
|
50
|
+
Renderers::JavascriptRenderer.expects(:new).at_least_once.returns(x)
|
51
|
+
Renderer.render(s)
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when rendering a page" do
|
55
|
+
setup do
|
56
|
+
@page = Page.new({ :layout => 'foo' }, 'bar')
|
57
|
+
@layout = Layout.new({}, 'layout: {{ yield }}')
|
58
|
+
Layout.expects(:find).with('foo').returns(@layout)
|
59
|
+
Layout.expects(:find).with(nil).returns(nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "wrap in layout" do
|
63
|
+
assert_equal("layout: <p>bar</p>\n", Renderer.render(@page))
|
64
|
+
end
|
65
|
+
|
66
|
+
should "take extra locals" do
|
67
|
+
Renderers::PageRenderer.any_instance.expects(:liquify).with("layout: <p>bar</p>\n", { :layout => 'foo', :title => 'bla' }).returns('foo')
|
68
|
+
Renderer.render(@page, :title => 'bla')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|