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,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
@@ -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