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.
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