magneto 0.1.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Don Melton
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,44 @@
1
+ # Magneto
2
+
3
+ Magneto is a static site generator.
4
+
5
+ ## About
6
+
7
+ Hi, I'm Don Melton. I wrote Magneto to generate [my own website](http://donmelton.com/). Magneto was inspired by [nanoc](http://nanoc.stoneship.org/) and [Jekyll](http://jekyllrb.com/), but it's a much simpler tool with fewer features and less policy.
8
+
9
+ For example, Magneto is not "blog aware" like some other systems, but it allows you to write a site controller script and plugins which can easily generate blog posts, an index page and a RSS feed. This is how I use it. There may be more work up front compared to other tools, but Magneto gives you very precise control over its behavior and output.
10
+
11
+ Before using Magneto, realize that it does have limitations due to its simplicity and that its programming interface may change because it's still under development.
12
+
13
+ ## Usage
14
+
15
+ magneto [OPTION]...
16
+
17
+ Source file items, their templates and the site controller script are loaded from the `items` and `templates` directories and from the `script.rb` file, all within the current directory. These are watched for changes and reloaded when automatic regeneration is enabled.
18
+
19
+ Ruby library files are loaded from the `plugins` directory only once.
20
+
21
+ The generated site is written to the `output` directory.
22
+
23
+ Configuration is loaded from `config.yaml` but can be overriden using the following options:
24
+
25
+ -c, --config PATH use specific YAML configuration file
26
+ -s, --source PATH use specific source directory
27
+ -o, --output PATH use specific output directory
28
+
29
+ --[no-]hidden include [exclude] hidden source files
30
+ --[no-]remove remove [keep] obsolete output
31
+ --[no-]auto enable [disable] automatic regeneration
32
+
33
+ -h, --help display this help and exit
34
+ --version output version information and exit
35
+
36
+ ## Dependencies
37
+
38
+ Magneto doesn't have any dependencies for basic operation.
39
+
40
+ Enabling automatic regeneration requires installation of the Directory Watcher gem:
41
+
42
+ sudo gem install directory_watcher
43
+
44
+ Using any of the built-in filters could require additional gem installations.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'magneto'
6
+
7
+ Magneto.application.run
@@ -0,0 +1,30 @@
1
+ require 'English'
2
+ require 'fileutils'
3
+ require 'optparse'
4
+ require 'rubygems'
5
+ require 'set'
6
+ require 'yaml'
7
+
8
+ require 'magneto/application'
9
+ require 'magneto/context'
10
+ require 'magneto/core_ext'
11
+ require 'magneto/filter'
12
+ require 'magneto/filters'
13
+ require 'magneto/item'
14
+ require 'magneto/readable'
15
+ require 'magneto/render_context'
16
+ require 'magneto/script_context'
17
+ require 'magneto/site'
18
+ require 'magneto/template'
19
+
20
+ module Magneto
21
+
22
+ VERSION = '0.1.0'
23
+
24
+ class << self
25
+
26
+ def application
27
+ @application ||= Application.new
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,153 @@
1
+ module Magneto
2
+
3
+ class Application
4
+
5
+ attr_reader :config, :filters, :site
6
+
7
+ def initialize
8
+ super
9
+ @options = {}
10
+ @config_path = 'config.yaml'
11
+ @config = {
12
+ :source_path => '.',
13
+ :output_path => 'output',
14
+ :hidden_files => false,
15
+ :remove_obsolete => false,
16
+ :auto_regeneration => false
17
+ }
18
+ @filters = {}
19
+ @site = nil
20
+ end
21
+
22
+ def run
23
+ parse_options
24
+ load_configuration
25
+
26
+ @config.deep_merge! @options
27
+
28
+ [:source_path, :output_path].each do |path|
29
+ @config[path] = File.expand_path(@config[path])
30
+ @config[path].freeze
31
+ end
32
+
33
+ load_plugins
34
+ find_filters
35
+
36
+ @site = Site.new(@config, @filters)
37
+
38
+ if @config[:auto_regeneration]
39
+ puts 'Automatic regeneration enabled.'
40
+
41
+ require 'directory_watcher'
42
+
43
+ dw = DirectoryWatcher.new(@config[:source_path])
44
+ dw.glob = [@config[:hidden_files] ? 'items/**/{.[^.],}*' : 'items/**/*', 'templates/*.*', 'script.rb']
45
+ dw.interval = 1
46
+ dw.add_observer { |*args| @site.generate }
47
+ dw.start
48
+ gets
49
+ dw.stop
50
+ else
51
+ @site.generate
52
+ end
53
+
54
+ exit
55
+ end
56
+
57
+ private
58
+
59
+ def parse_options
60
+ begin
61
+ OptionParser.new do |opts|
62
+ opts.banner = "Magneto is a static site generator."
63
+ opts.separator ""
64
+ opts.separator " Usage: #{File.basename($PROGRAM_NAME)} [OPTION]..."
65
+ opts.separator ""
66
+ opts.separator " Source file items, their templates and the site controller script"
67
+ opts.separator " are loaded from the 'items' and 'templates' directories and from the"
68
+ opts.separator " 'script.rb' file, all within the current directory. These are watched"
69
+ opts.separator " for changes and reloaded when automatic regeneration is enabled."
70
+ opts.separator ""
71
+ opts.separator " Ruby library files are loaded from the 'plugins' directory only once."
72
+ opts.separator ""
73
+ opts.separator " The generated site is written to the 'output' directory."
74
+ opts.separator ""
75
+ opts.separator " Configuration is loaded from 'config.yaml' but can be overriden"
76
+ opts.separator " using the following options:"
77
+ opts.separator ""
78
+
79
+ opts.on('-c', '--config PATH', 'use specific YAML configuration file') { |cp| @config_path = cp }
80
+ opts.on('-s', '--source PATH', 'use specific source directory') { |sp| @options[:source_path] = sp }
81
+ opts.on('-o', '--output PATH', 'use specific output directory') { |op| @options[:output_path] = op }
82
+
83
+ opts.separator ""
84
+
85
+ opts.on('--[no-]hidden', 'include [exclude] hidden source files') { |hf| @options[:hidden_files] = hf }
86
+ opts.on('--[no-]remove', 'remove [keep] obsolete output') { |ro| @options[:remove_obsolete] = ro }
87
+ opts.on('--[no-]auto', 'enable [disable] automatic regeneration') { |ar| @options[:auto_regeneration] = ar }
88
+
89
+ opts.separator ""
90
+
91
+ opts.on_tail('-h', '--help', 'display this help and exit') do
92
+ puts opts
93
+ exit
94
+ end
95
+
96
+ opts.on_tail '--version', 'output version information and exit' do
97
+ puts <<VERSION_HERE
98
+ #{File.basename($PROGRAM_NAME)} #{VERSION}
99
+ Copyright (c) 2012 Don Melton
100
+ This is free software; see the source for copying conditions. There is NO
101
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
102
+ VERSION_HERE
103
+ exit
104
+ end
105
+ end.parse!
106
+ rescue => ex
107
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
108
+ usage
109
+ end
110
+
111
+ if ARGV.size > 0
112
+ $stderr.print "#{File.basename($PROGRAM_NAME)}: unknown argument(s):"
113
+ ARGV.each { |a| $stderr.print " #{a}" }
114
+ $stderr.puts
115
+ usage
116
+ end
117
+ end
118
+
119
+ def usage
120
+ $stderr.puts "Try '#{File.basename($PROGRAM_NAME)} --help' for more information."
121
+ exit false
122
+ end
123
+
124
+ def load_configuration
125
+ puts 'Loading configuration...'
126
+
127
+ begin
128
+ configuration = YAML.load_file(@config_path)
129
+ raise unless configuration.is_a? Hash
130
+ rescue => ex
131
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
132
+ $stderr.puts "WARNING: Couldn't load configuration. Using defaults (and options)."
133
+ configuration = {}
134
+ end
135
+
136
+ @config.deep_merge! configuration.symbolize_keys
137
+ end
138
+
139
+ def load_plugins
140
+ puts 'Loading plugins...'
141
+ Dir[@config[:source_path] + '/plugins/**/*.rb'].each { |plugin| require plugin unless File.directory? plugin }
142
+ end
143
+
144
+ def find_filters
145
+ puts 'Finding filters...'
146
+
147
+ Magneto::Filter.subclasses.each do |subclass|
148
+ filter = subclass.new(@config)
149
+ @filters[filter.name.to_sym] = filter
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,21 @@
1
+ module Magneto
2
+
3
+ # Adapted from `nanoc/base/context.rb` with thanks to Denis Defreyne and
4
+ # contributors.
5
+ class Context
6
+
7
+ def initialize(ivars)
8
+ super()
9
+ eigenclass = class << self ; self ; end
10
+
11
+ ivars.each do |symbol, value|
12
+ instance_variable_set('@' + symbol.to_s, value)
13
+ eigenclass.send(:define_method, symbol) { value } unless eigenclass.send(:respond_to?, symbol)
14
+ end
15
+ end
16
+
17
+ def get_binding
18
+ binding
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,58 @@
1
+ class Hash
2
+
3
+ # Copied from `active_support/core_ext/hash/keys.rb` with thanks to David
4
+ # Heinemeier Hansson and contributors.
5
+
6
+ # Return a new hash with all keys converted to strings.
7
+ def stringify_keys
8
+ dup.stringify_keys!
9
+ end
10
+
11
+ # Destructively convert all keys to strings.
12
+ def stringify_keys!
13
+ keys.each do |key|
14
+ self[key.to_s] = delete(key)
15
+ end
16
+ self
17
+ end
18
+
19
+ # Return a new hash with all keys converted to symbols, as long as
20
+ # they respond to +to_sym+.
21
+ def symbolize_keys
22
+ dup.symbolize_keys!
23
+ end
24
+
25
+ # Destructively convert all keys to symbols, as long as they respond
26
+ # to +to_sym+.
27
+ def symbolize_keys!
28
+ keys.each do |key|
29
+ self[(key.to_sym rescue key) || key] = delete(key)
30
+ end
31
+ self
32
+ end
33
+
34
+ # Adapted from `jekyll/core_ext.rb` with thanks to Tom Preston-Werner.
35
+
36
+ # Merges self with another hash, recursively.
37
+ #
38
+ # This code was lovingly stolen (now adapted) from some random gem:
39
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
40
+ #
41
+ # Thanks to whoever made it.
42
+ def deep_merge(hash)
43
+ dup.deep_merge! hash
44
+ end
45
+
46
+ def deep_merge!(hash)
47
+ hash.keys.each do |key|
48
+ if hash[key].is_a? Hash and self[key].is_a? Hash
49
+ self[key] = self[key].deep_merge(hash[key])
50
+ next
51
+ end
52
+
53
+ self[key] = hash[key]
54
+ end
55
+
56
+ self
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ module Magneto
2
+
3
+ # Adapted from `jekyll/plugin.rb` with thanks to Tom Preston-Werner.
4
+ class Filter
5
+
6
+ class << self
7
+
8
+ def inherited(subclass)
9
+ subclasses << subclass
10
+ end
11
+
12
+ def subclasses
13
+ @subclasses ||= []
14
+ end
15
+ end
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ require 'magneto/filters/bluecloth'
2
+ require 'magneto/filters/coffeescript'
3
+ require 'magneto/filters/erb'
4
+ require 'magneto/filters/erubis'
5
+ require 'magneto/filters/haml'
6
+ require 'magneto/filters/kramdown'
7
+ require 'magneto/filters/less'
8
+ require 'magneto/filters/maruku'
9
+ require 'magneto/filters/rdiscount'
10
+ require 'magneto/filters/redcarpet'
11
+ require 'magneto/filters/redcloth'
12
+ require 'magneto/filters/rubypants'
13
+ require 'magneto/filters/sass'
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class BlueClothFilter < Filter
4
+
5
+ def name
6
+ 'bluecloth'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'bluecloth'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use BlueCloth. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install bluecloth'
16
+ raise 'Missing dependency: bluecloth'
17
+ end
18
+
19
+ BlueCloth.new(content, (ivars[:bluecloth].symbolize_keys rescue {})).to_html
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class CoffeeScriptFilter < Filter
4
+
5
+ def name
6
+ 'coffeescript'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'coffee-script'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use CoffeeScript. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install coffee-script'
16
+ raise 'Missing dependency: coffee-script'
17
+ end
18
+
19
+ CoffeeScript.compile(content, (ivars[:coffeescript].symbolize_keys rescue {}))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class ERBFilter < Filter
4
+
5
+ def name
6
+ 'erb'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ require 'erb'
11
+
12
+ # Adapted from `nanoc/filters/erb.rb` with thanks to Denis Defreyne and
13
+ # contributors.
14
+ context = RenderContext.new(ivars)
15
+ proc = ivars[:content] ? lambda { ivars[:content] } : lambda {}
16
+ b = context.get_binding(&proc)
17
+
18
+ args = ivars[:erb].symbolize_keys rescue {}
19
+ ERB.new(content, args[:safe_level], args[:trim_mode]).result b
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ module Magneto
2
+
3
+ class ErubisFilter < Filter
4
+
5
+ def name
6
+ 'erubis'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'erubis'
12
+ require 'erubis/engine/enhanced'
13
+ rescue LoadError => ex
14
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
15
+ $stderr.puts "You're missing a library required to use Erubis. Try running:"
16
+ $stderr.puts ' $ [sudo] gem install erubis'
17
+ raise 'Missing dependency: erubis'
18
+ end
19
+
20
+ # Adapted from "nanoc/filters/erubis.rb" with thanks to Denis Defreyne and contributors.
21
+ context = RenderContext.new(ivars)
22
+ proc = ivars[:content] ? lambda { ivars[:content] } : lambda {}
23
+ b = context.get_binding(&proc)
24
+
25
+ Erubis::ErboutEruby.new(content, (ivars[:erubis].symbolize_keys rescue {})).result b
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Magneto
2
+
3
+ class HamlFilter < Filter
4
+
5
+ def name
6
+ 'haml'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'haml'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use Haml. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install haml'
16
+ raise 'Missing dependency: haml'
17
+ end
18
+
19
+ # Adapted from "nanoc/filters/haml.rb" with thanks to Denis Defreyne and contributors.
20
+ context = RenderContext.new(ivars)
21
+ proc = ivars[:content] ? lambda { ivars[:content] } : lambda {}
22
+
23
+ Haml::Engine.new(content, (ivars[:haml].symbolize_keys rescue {})).render(context, ivars, &proc)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class KramdownFilter < Filter
4
+
5
+ def name
6
+ 'kramdown'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'kramdown'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use kramdown. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install kramdown'
16
+ raise 'Missing dependency: kramdown'
17
+ end
18
+
19
+ Kramdown::Document.new(content, (ivars[:kramdown].symbolize_keys rescue {})).to_html
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Magneto
2
+
3
+ class LessFilter < Filter
4
+
5
+ def name
6
+ 'less'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'less'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use Less. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install less'
16
+ raise 'Missing dependency: less'
17
+ end
18
+
19
+ args = ivars[:less].symbolize_keys rescue {}
20
+ Less::Parser.new(args).parse(content).to_css(args)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class MarukuFilter < Filter
4
+
5
+ def name
6
+ 'maruku'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'maruku'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use Maruku. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install maruku'
16
+ raise 'Missing dependency: maruku'
17
+ end
18
+
19
+ Maruku.new(content, (ivars[:maruku].symbolize_keys rescue {})).to_html
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class RDiscountFilter < Filter
4
+
5
+ def name
6
+ 'rdiscount'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'rdiscount'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use RDiscount. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install rdiscount'
16
+ raise 'Missing dependency: rdiscount'
17
+ end
18
+
19
+ RDiscount.new(content, *((ivars[:rdiscount].symbolize_keys rescue {})[:extensions] || [])).to_html
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Magneto
2
+
3
+ class RedcarpetFilter < Filter
4
+
5
+ def name
6
+ 'redcarpet'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'redcarpet'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use Redcarpet. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install redcarpet'
16
+ raise 'Missing dependency: redcarpet'
17
+ end
18
+
19
+ args = ivars[:redcarpet].symbolize_keys rescue {}
20
+ renderer_class = Kernel.const_get(args[:renderer_name]) rescue args[:renderer_class] || Redcarpet::Render::HTML
21
+ args.delete(:renderer_name)
22
+ args.delete(:renderer_class)
23
+ renderer_options = args[:renderer_options].symbolize_keys rescue {}
24
+ args.delete(:renderer_options)
25
+
26
+ Redcarpet::Markdown.new(renderer_class.new(renderer_options), args).render(content)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Magneto
2
+
3
+ class RedClothFilter < Filter
4
+
5
+ def name
6
+ 'redcloth'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'redcloth'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use RedCloth. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install RedCloth'
16
+ raise 'Missing dependency: RedCloth'
17
+ end
18
+
19
+ textile_doc = RedCloth.new(content)
20
+ ivars[:redcloth].each { |rule, value| textile_doc.send((rule.to_s + '=').to_sym, value) }
21
+ textile_doc.to_html
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class RubyPantsFilter < Filter
4
+
5
+ def name
6
+ 'rubypants'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'rubypants'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use RubyPants. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install rubypants'
16
+ raise 'Missing dependency: rubypants'
17
+ end
18
+
19
+ RubyPants.new(content, *((ivars[:rubypants].symbolize_keys rescue {})[:options] || [])).to_html
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Magneto
2
+
3
+ class SassFilter < Filter
4
+
5
+ def name
6
+ 'sass'
7
+ end
8
+
9
+ def apply(content, ivars)
10
+ begin
11
+ require 'sass'
12
+ rescue LoadError => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ $stderr.puts "You're missing a library required to use Sass. Try running:"
15
+ $stderr.puts ' $ [sudo] gem install sass'
16
+ raise 'Missing dependency: sass'
17
+ end
18
+
19
+ Sass::Engine.new(content, (ivars[:sass].symbolize_keys rescue {})).render
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ require 'magneto/readable'
2
+
3
+ module Magneto
4
+
5
+ class Item
6
+ include Readable
7
+
8
+ attr_reader :site, :origin
9
+
10
+ INVALID_LOCATION_MATCH_PATTERN = %r{(^[^/].*$|^.*/$)}
11
+
12
+ def initialize(site, origin = '')
13
+ super()
14
+ @site = site
15
+ @origin = origin.sub(INVALID_LOCATION_MATCH_PATTERN, '')
16
+ @destination = nil
17
+ @metadata = nil
18
+ @content = nil
19
+ @precomposed_content = nil
20
+ end
21
+
22
+ def relocated?
23
+ @origin == ''
24
+ end
25
+
26
+ def relocate
27
+ @origin = ''
28
+ end
29
+
30
+ def abandoned?
31
+ @destination == ''
32
+ end
33
+
34
+ def abandon
35
+ @destination = ''
36
+ end
37
+
38
+ def destination
39
+ @destination ||= @origin.dup
40
+ @destination
41
+ end
42
+
43
+ def destination=(destination)
44
+ @destination = (destination || '').sub(INVALID_LOCATION_MATCH_PATTERN, '')
45
+ end
46
+
47
+ def origin_path
48
+ @site.items_path + @origin
49
+ end
50
+
51
+ alias_method :path, :origin_path
52
+
53
+ def destination_path
54
+ @site.config[:output_path] + (@destination || @origin)
55
+ end
56
+
57
+ def import_metadata
58
+ self.destination = @metadata[:destination] unless @metadata.nil? || @metadata[:destination].nil?
59
+ end
60
+
61
+ def precomposed_content
62
+ read if @content.nil?
63
+ @precomposed_content || @content.dup
64
+ end
65
+
66
+ def apply_filter(filter_name, args = {})
67
+ filter = @site.filters[filter_name.to_sym]
68
+
69
+ if filter.nil?
70
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
71
+ raise "Couldn't find filter: '#{filter_name.to_s}'"
72
+ end
73
+
74
+ read if @content.nil?
75
+
76
+ @content = filter.apply(@content, @site.config.deep_merge(@metadata || {}).deep_merge({
77
+ :config => @site.config,
78
+ :site => @site,
79
+ :item => self
80
+ }).deep_merge(filter_name.to_sym => args))
81
+ end
82
+
83
+ def apply_template(template_name, args = {})
84
+ template = @site.templates[template_name.to_sym]
85
+
86
+ if template.nil?
87
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
88
+ raise "Couldn't find template: '#{template_name.to_s}'"
89
+ end
90
+
91
+ read if @content.nil?
92
+ @precomposed_content ||= @content.dup
93
+
94
+ @content = template.filter.apply(template.content, {
95
+ template.filter.name.to_sym => {}
96
+ }.deep_merge(@site.config).deep_merge(@metadata || {}).deep_merge(template.metadata || {}).deep_merge({
97
+ :config => @site.config,
98
+ :site => @site,
99
+ :item => self,
100
+ :content => @content
101
+ }).deep_merge(args.symbolize_keys))
102
+ end
103
+
104
+ def write
105
+ unless abandoned?
106
+ if content?
107
+ FileUtils.mkdir_p File.dirname(destination_path)
108
+ File.open(destination_path, 'w') { |f| f.write content }
109
+ else
110
+ unless relocated?
111
+ FileUtils.mkdir_p File.dirname(destination_path)
112
+ FileUtils.cp origin_path, destination_path
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,55 @@
1
+ module Magneto
2
+
3
+ module Readable
4
+
5
+ def metadata?
6
+ not @metadata.nil? || @metadata.empty?
7
+ end
8
+
9
+ def metadata
10
+ read if @metadata.nil?
11
+ @metadata || {}
12
+ end
13
+
14
+ def metadata=(metadata)
15
+ @metadata = metadata || {}
16
+ end
17
+
18
+ def content?
19
+ not @content.nil? || @content.empty?
20
+ end
21
+
22
+ def content
23
+ read if @content.nil?
24
+ @content || ''
25
+ end
26
+
27
+ alias to_s content
28
+
29
+ def content=(content)
30
+ @content = content || ''
31
+ end
32
+
33
+ # Adapted from `jekyll/convertible.rb` with thanks to Tom Preston-Werner.
34
+ def read
35
+ @metadata = {}
36
+ @content = File.read(path)
37
+
38
+ if @content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
39
+ @content = $POSTMATCH
40
+
41
+ begin
42
+ @metadata = YAML.load($1)
43
+ raise unless @metadata.is_a? Hash
44
+ rescue => ex
45
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
46
+ $stderr.puts "WARNING: Couldn't load metadata."
47
+ @metadata = {}
48
+ end
49
+
50
+ @metadata.symbolize_keys!
51
+ import_metadata
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ module Magneto
2
+
3
+ # Adapted from `nanoc/helpers/rendering.rb` and `nanoc/helpers/capturing.rb`
4
+ # with thanks to Denis Defreyne and contributors.
5
+ class RenderContext < Context
6
+
7
+ def render(template_name, args = {}, &block)
8
+ template = @site.templates[template_name.to_sym]
9
+
10
+ if template.nil?
11
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
12
+ raise "Couldn't find template: '#{template_name.to_s}'"
13
+ end
14
+
15
+ if block_given?
16
+ # Get templating system output instance.
17
+ erbout = eval('_erbout', block.binding)
18
+
19
+ # Save output length.
20
+ erbout_length = erbout.length
21
+
22
+ # Execute block (which may cause recursion).
23
+ block.call
24
+
25
+ # Use additional output from block execution as content.
26
+ current_content = erbout[erbout_length..-1]
27
+
28
+ # Remove addition from templating system output.
29
+ erbout[erbout_length..-1] = ''
30
+ else
31
+ current_content = nil
32
+ end
33
+
34
+ ivars = {}
35
+ self.instance_variables.each { |ivar| ivars[ivar[1..-1].to_sym] = self.instance_variable_get(ivar) }
36
+
37
+ result = template.filter.apply(template.content, ivars.deep_merge({
38
+ :content => current_content
39
+ }).deep_merge(template.metadata || {}).deep_merge(args.symbolize_keys))
40
+
41
+ if block_given?
42
+ # Append filter result to templating system output and return empty
43
+ # string.
44
+ erbout << result
45
+ ''
46
+ else
47
+ result
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ module Magneto
2
+
3
+ class ScriptContext < Context
4
+
5
+ def initialize(ivars)
6
+ super
7
+ puts 'Evaluating script...'
8
+
9
+ begin
10
+ self.instance_eval File.read(@source_path + '/script.rb')
11
+ @site.write
12
+ rescue => ex
13
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
14
+ raise 'Script evaluation failed.'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,116 @@
1
+ module Magneto
2
+
3
+ class Site
4
+
5
+ attr_reader :config, :filters, :items_path, :items, :templates
6
+
7
+ def initialize(config, filters)
8
+ super()
9
+ @config = config
10
+ @filters = filters
11
+ @items_path = @config[:source_path] + '/items'
12
+ @items_path.freeze
13
+ reset
14
+ end
15
+
16
+ def generate
17
+ find_items
18
+ find_templates
19
+ find_existing_output
20
+
21
+ ScriptContext.new(@config.deep_merge({
22
+ :config => @config,
23
+ :site => self
24
+ }))
25
+
26
+ reset
27
+ end
28
+
29
+ def write
30
+ return if @written
31
+
32
+ puts 'Writing output...'
33
+ @written = true
34
+ output = Set.new unless @existing_output.empty?
35
+
36
+ @items.each do |item|
37
+ item.write
38
+
39
+ unless @existing_output.empty?
40
+ next if item.abandoned?
41
+ path = item.destination_path
42
+
43
+ while path.start_with? @config[:output_path] do
44
+ output << path
45
+ path = File.dirname(path)
46
+ end
47
+ end
48
+ end
49
+
50
+ unless @existing_output.empty?
51
+ obsolete_output = @existing_output - output
52
+
53
+ unless obsolete_output.empty?
54
+ if @config[:remove_obsolete]
55
+ puts 'Removing obsolete output...'
56
+
57
+ obsolete_output.to_a.sort.reverse.each do |path|
58
+ if File.directory? path
59
+ FileUtils.rmdir path
60
+ else
61
+ FileUtils.rm path
62
+ end
63
+ end
64
+ else
65
+ puts 'Listing obsolete output...'
66
+ puts obsolete_output.to_a.sort.reverse
67
+ end
68
+ end
69
+ end
70
+
71
+ puts 'Site generation succeeded.'
72
+ end
73
+
74
+ private
75
+
76
+ def reset
77
+ @items = []
78
+ @templates = {}
79
+ @existing_output = Set.new
80
+ @written = false
81
+ end
82
+
83
+ def find_items
84
+ puts 'Finding items...'
85
+
86
+ if File.directory? @items_path
87
+ Dir.chdir @items_path do
88
+ Dir[@config[:hidden_files] ? '**/{.[^.],}*' : '**/*'].each do |path|
89
+ unless File.directory? path
90
+ @items << Item.new(self, '/' + path)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def find_templates
98
+ puts 'Finding templates...'
99
+
100
+ Dir[@config[:source_path] + '/templates/*.*'].each do |path|
101
+ unless File.directory? path
102
+ name = File.basename(path).split('.')[0..-2].join('.')
103
+ @templates[name.to_sym] = Template.new(self, path, name)
104
+ end
105
+ end
106
+ end
107
+
108
+ def find_existing_output
109
+ puts 'Finding existing output...'
110
+
111
+ Dir[@config[:output_path] + '/**/{.[^.],}*'].each do |path|
112
+ @existing_output << path
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,31 @@
1
+ module Magneto
2
+
3
+ class Template
4
+ include Readable
5
+
6
+ attr_reader :site, :path, :name, :filter
7
+
8
+ def initialize(site, path, name)
9
+ super()
10
+ @site = site
11
+ @path = path
12
+ @name = name
13
+ @filter = nil
14
+ @metadata = nil
15
+ @content = nil
16
+ end
17
+
18
+ def use_filter(filter_name)
19
+ @filter = @site.filters[filter_name.to_sym]
20
+
21
+ if @filter.nil?
22
+ $stderr.puts "#{File.basename($PROGRAM_NAME)}: #{ex.to_s}"
23
+ raise "Couldn't find filter: '#{filter_name.to_s}'"
24
+ end
25
+ end
26
+
27
+ def import_metadata
28
+ @filter = use_filter(@metadata[:filter]) unless @metadata.nil? || @metadata[:filter].nil?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
2
+
3
+ require 'magneto'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'magneto'
7
+ s.version = Magneto::VERSION
8
+ s.summary = 'A static site generator.'
9
+ s.description = 'Magneto is a static site generator.'
10
+ s.authors = ['Don Melton']
11
+ s.email = 'don@blivet.com'
12
+ s.homepage = 'https://github.com/donmelton/magneto'
13
+ s.files = Dir['{bin,lib}/**/*'] + Dir['[A-Z]*'] + ['magneto.gemspec']
14
+ s.executables = ['magneto']
15
+ s.extra_rdoc_files = ['LICENSE']
16
+ s.require_paths = ['lib']
17
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magneto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Don Melton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Magneto is a static site generator.
15
+ email: don@blivet.com
16
+ executables:
17
+ - magneto
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - LICENSE
21
+ files:
22
+ - bin/magneto
23
+ - lib/magneto/application.rb
24
+ - lib/magneto/context.rb
25
+ - lib/magneto/core_ext.rb
26
+ - lib/magneto/filter.rb
27
+ - lib/magneto/filters/bluecloth.rb
28
+ - lib/magneto/filters/coffeescript.rb
29
+ - lib/magneto/filters/erb.rb
30
+ - lib/magneto/filters/erubis.rb
31
+ - lib/magneto/filters/haml.rb
32
+ - lib/magneto/filters/kramdown.rb
33
+ - lib/magneto/filters/less.rb
34
+ - lib/magneto/filters/maruku.rb
35
+ - lib/magneto/filters/rdiscount.rb
36
+ - lib/magneto/filters/redcarpet.rb
37
+ - lib/magneto/filters/redcloth.rb
38
+ - lib/magneto/filters/rubypants.rb
39
+ - lib/magneto/filters/sass.rb
40
+ - lib/magneto/filters.rb
41
+ - lib/magneto/item.rb
42
+ - lib/magneto/readable.rb
43
+ - lib/magneto/render_context.rb
44
+ - lib/magneto/script_context.rb
45
+ - lib/magneto/site.rb
46
+ - lib/magneto/template.rb
47
+ - lib/magneto.rb
48
+ - LICENSE
49
+ - README.md
50
+ - magneto.gemspec
51
+ homepage: https://github.com/donmelton/magneto
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.24
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: A static site generator.
75
+ test_files: []