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