laze 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +150 -0
  5. data/Rakefile +76 -0
  6. data/VERSION.yml +5 -0
  7. data/bin/laze +126 -0
  8. data/examples/website/includes/tagline.html +1 -0
  9. data/examples/website/input/contact/email.md +6 -0
  10. data/examples/website/input/css/screen.css +5 -0
  11. data/examples/website/input/css/test.less +6 -0
  12. data/examples/website/input/img/ruby.gif +0 -0
  13. data/examples/website/input/img/test.jpg +0 -0
  14. data/examples/website/input/img/test.png +0 -0
  15. data/examples/website/input/index.md +13 -0
  16. data/examples/website/input/js/foo.js +1 -0
  17. data/examples/website/input/js/lib.js +5 -0
  18. data/examples/website/layouts/default.html +15 -0
  19. data/examples/website/layouts/subpage.html +5 -0
  20. data/examples/website/laze.yml +1 -0
  21. data/features/create_sites.feature +73 -0
  22. data/features/plugins.feature +49 -0
  23. data/features/site_data.feature +26 -0
  24. data/features/step_definitions/laze_steps.rb +77 -0
  25. data/features/support/env.rb +17 -0
  26. data/lib/laze.rb +64 -0
  27. data/lib/laze/asset.rb +7 -0
  28. data/lib/laze/core_extensions.rb +15 -0
  29. data/lib/laze/item.rb +59 -0
  30. data/lib/laze/javascript.rb +5 -0
  31. data/lib/laze/layout.rb +105 -0
  32. data/lib/laze/page.rb +33 -0
  33. data/lib/laze/plugins.rb +56 -0
  34. data/lib/laze/plugins/cache_buster.rb +49 -0
  35. data/lib/laze/plugins/css_imports.rb +42 -0
  36. data/lib/laze/plugins/cssmin.rb +31 -0
  37. data/lib/laze/plugins/image_check.rb +28 -0
  38. data/lib/laze/plugins/image_optimizer.rb +52 -0
  39. data/lib/laze/plugins/js_requires.rb +63 -0
  40. data/lib/laze/plugins/jsmin.rb +31 -0
  41. data/lib/laze/plugins/less.rb +35 -0
  42. data/lib/laze/plugins/robots.rb +28 -0
  43. data/lib/laze/plugins/sitemap.rb +63 -0
  44. data/lib/laze/renderer.rb +47 -0
  45. data/lib/laze/renderers/javascript_renderer.rb +16 -0
  46. data/lib/laze/renderers/page_renderer.rb +40 -0
  47. data/lib/laze/renderers/stylesheet_renderer.rb +16 -0
  48. data/lib/laze/secretary.rb +74 -0
  49. data/lib/laze/section.rb +39 -0
  50. data/lib/laze/store.rb +51 -0
  51. data/lib/laze/stores/filesystem.rb +127 -0
  52. data/lib/laze/stylesheet.rb +6 -0
  53. data/lib/laze/target.rb +56 -0
  54. data/lib/laze/targets/filesystem.rb +41 -0
  55. data/test/helper.rb +12 -0
  56. data/test/test_assets.rb +28 -0
  57. data/test/test_core_extensions.rb +19 -0
  58. data/test/test_item.rb +59 -0
  59. data/test/test_layout.rb +47 -0
  60. data/test/test_renderer.rb +71 -0
  61. data/test/test_renderers.rb +40 -0
  62. data/test/test_secretary.rb +48 -0
  63. data/test/test_store.rb +18 -0
  64. data/test/test_target.rb +84 -0
  65. metadata +207 -0
@@ -0,0 +1,5 @@
1
+ module Laze
2
+ # A special kind of Item aimed at javascript files.
3
+ class Javascript < Asset
4
+ end
5
+ end
@@ -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
@@ -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