plato 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ /pkg
3
+ /content
4
+ /template
5
+ /resources
6
+ /config.rb
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ ROOT_DIR = File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'rubygems' rescue nil
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+
7
+ task :default => :spec
8
+
9
+ desc "Run all specs in spec directory."
10
+ Spec::Rake::SpecTask.new(:spec) do |t|
11
+ t.spec_opts = ['--options', "\"#{ROOT_DIR}/spec/spec.opts\""]
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ # gemification with jeweler
16
+ begin
17
+ require 'jeweler'
18
+ Jeweler::Tasks.new do |gemspec|
19
+ gemspec.name = "plato"
20
+ gemspec.summary = "An ideal static site generator"
21
+ gemspec.description = "use templates and content to generate static sites."
22
+ gemspec.email = "matt@freels.name"
23
+ gemspec.homepage = "http://github.com/freels/plato"
24
+ gemspec.authors = ["Matt Freels"]
25
+ gemspec.add_dependency 'tilt', '>= 1.0.1'
26
+
27
+ # development
28
+ gemspec.add_development_dependency 'rspec'
29
+ gemspec.add_development_dependency 'rr'
30
+ end
31
+ rescue LoadError
32
+ puts "Jeweler not available. Install it with: gem install jeweler"
33
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,65 @@
1
+ module Plato
2
+ module Config
3
+ extend self
4
+
5
+ def read(dsl_class, string = nil, &block)
6
+ config = dsl_class.new
7
+
8
+ if string
9
+ config.instance_eval(string)
10
+ else
11
+ block.arity == 1 ? call.block(config) : config.instance_eval(&block)
12
+ end
13
+
14
+ extract_ivars(config)
15
+ end
16
+
17
+ private
18
+
19
+ def extract_ivars(config)
20
+ config.instance_variables.inject({}) do |result, ivar|
21
+ result.update(ivar.sub('@', '') => config.instance_variable_get(ivar))
22
+ end
23
+ end
24
+ end
25
+
26
+ class ConfigDSL
27
+ # Config DSL
28
+
29
+ def base_url(url)
30
+ @base_url = url
31
+ end
32
+ alias url base_url
33
+
34
+ def options(hash)
35
+ @options = hash
36
+ end
37
+
38
+ def content(name, content_path_template, opts = {})
39
+ @content_categories ||= {}
40
+ @content_categories[name.to_s] =
41
+ ContentCategory.new(name, content_path_template,
42
+ opts[:to] || content_path_template, opts[:sort], opts[:template])
43
+ end
44
+ end
45
+
46
+ class ContentCategory
47
+ attr_reader :name, :documents, :src_parser, :dest_parser, :template
48
+
49
+ def initialize(name, src_t, dest_t, sort, template)
50
+ @name = name
51
+ @documents = DocumentCollection.new(sort)
52
+ @src_parser = PathTemplate.new(src_t)
53
+ @dest_parser = PathTemplate.new(dest_t)
54
+ @template = "_#{template}" if template
55
+ end
56
+
57
+ def match(path); src_parser.parse(path) end
58
+ def dest_path(data); dest_parser.materialize(data) end
59
+
60
+ def method_missing(method, *args, &block)
61
+ documents.send(method, *args, &block)
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,131 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ module Plato
5
+ class Document
6
+ attr_reader :category, :attributes
7
+
8
+ def initialize(category, attributes)
9
+ @category = category
10
+ @attributes = attributes
11
+ end
12
+
13
+ def previous_document
14
+ category.documents.prev(self)
15
+ end
16
+
17
+ def next_document
18
+ category.documents.next(self)
19
+ end
20
+
21
+ def format
22
+ attributes["format"] || 'text'
23
+ end
24
+
25
+ def path
26
+ category.dest_path(self)
27
+ end
28
+
29
+ def date
30
+ @date ||= (attributes["date"] ? Time.parse(attributes["date"]) : nil)
31
+ end
32
+
33
+ RAW_TEXT = %w(text txt raw)
34
+
35
+ def body(context = nil)
36
+ if RAW_TEXT.include? format
37
+ attributes["body"]
38
+ else
39
+ @template ||= Tilt.new(format) { attributes["body"] }
40
+ @template.render(context)
41
+ end
42
+ end
43
+
44
+ def values_at(*keys)
45
+ keys.map {|k| send(k) }
46
+ end
47
+
48
+ def respond_to?(attr)
49
+ attributes.has_key? attr.to_s or super
50
+ end
51
+
52
+ def method_missing(attr)
53
+ if date and date.respond_to? attr
54
+ date.send(attr)
55
+ else
56
+ attributes[attr.to_s] or super
57
+ end
58
+ end
59
+ end
60
+
61
+ class DocumentCollection
62
+ include Enumerable
63
+
64
+ attr_accessor :sort_attribute, :sort_order
65
+
66
+ def initialize(sort = nil)
67
+ @documents = []
68
+ @sort_attribute, @sort_order = sort.split(' ') if sort
69
+ end
70
+
71
+ def <<(doc)
72
+ @to_a = nil
73
+ @documents << doc
74
+ end
75
+
76
+ def [](*args); to_a.[](*args) end
77
+
78
+ def index(doc, strict = false)
79
+ unless index = to_a.index(doc)
80
+ raise ArgumentError, "document is not a member of this collection" unless strict
81
+ end
82
+ index
83
+ end
84
+
85
+ def first
86
+ to_a.first
87
+ end
88
+
89
+ def prev(doc)
90
+ idx = index(doc)
91
+ idx.zero? ? nil : to_a[idx - 1]
92
+ end
93
+
94
+ def next(doc)
95
+ to_a[index(doc) + 1]
96
+ end
97
+
98
+ def to_a
99
+ sort! unless @to_a
100
+ @to_a.dup
101
+ end
102
+
103
+ def each(&block)
104
+ to_a.each(&block)
105
+ end
106
+
107
+ def sort!
108
+ @to_a =
109
+ if sorted?
110
+ @documents.sort! do |a, b|
111
+ a,b = [a, b].map {|d| d.send(sort_attribute) }
112
+ ascending? ? a <=> b : b <=> a
113
+ end
114
+ else
115
+ @documents
116
+ end
117
+ end
118
+
119
+ def sorted?
120
+ !!@sort_attribute
121
+ end
122
+
123
+ def descending?
124
+ sorted? && !!(sort_order =~ /^desc/i)
125
+ end
126
+
127
+ def ascending?
128
+ sorted? && !descending?
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,57 @@
1
+ module Plato
2
+ class Manifest
3
+ include Enumerable
4
+
5
+ attr_reader :contents, :codec
6
+
7
+ def initialize(contents = nil, opts = {})
8
+ opts = { :codec => opts } unless opts.is_a? Hash
9
+ @codec = opts[:codec]
10
+
11
+ @contents =
12
+ if contents.nil?
13
+ {}
14
+ elsif contents.is_a? Hash
15
+ contents
16
+ elsif contents.is_a? String
17
+ Repo.new(contents, @codec).all &opts[:filter]
18
+ else
19
+ raise ArgumentError, "invalid contents"
20
+ end
21
+ end
22
+
23
+ def save_to(path, codec = nil)
24
+ repo = Repo.new(path, codec || self.codec)
25
+ @contents.each {|path, hash| repo.save(path, hash) }
26
+ end
27
+
28
+ def [](key)
29
+ contents[key]
30
+ end
31
+
32
+ def []=(key, value)
33
+ contents[key] = value
34
+ end
35
+ alias store []=
36
+
37
+ # if given a block, block should return a hash of the new path and
38
+ # new data, or nil if the file should be skipped
39
+ def map(new_codec = nil)
40
+ new_contents =
41
+ if block_given?
42
+ @contents.inject({}) do |hash, (path, data)|
43
+ new_path_data = yield(path, data)
44
+ new_path_data ? hash.update(new_path_data) : hash
45
+ end
46
+ else
47
+ @contents.dup
48
+ end
49
+
50
+ self.class.new(new_contents, new_codec || self.codec)
51
+ end
52
+
53
+ def each(&block)
54
+ contents.each(&block)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ module Plato
2
+ class PathTemplate
3
+ attr_reader :template, :keys
4
+
5
+ def initialize(string)
6
+ @template = string
7
+ compile!
8
+ end
9
+
10
+ def materialize(attributes)
11
+ values = attributes.values_at(*keys).compact
12
+ unless values.length == keys.length
13
+ raise ArgumentError, "missing required values for path materialization (#{keys.join(', ')})"
14
+ end
15
+
16
+ @materializer.call(values)
17
+ end
18
+
19
+ def parse(path)
20
+ match = path.match(@parser)
21
+ return nil unless match
22
+
23
+ match = match.to_a
24
+ match.shift
25
+
26
+ Hash[*keys.zip(match).flatten]
27
+ end
28
+
29
+ private
30
+
31
+ # basically lifted from sinatra
32
+
33
+ SCANNER = /(:\w+(?:\\\*)?)/
34
+
35
+ def compile!
36
+ @keys = []
37
+
38
+ pattern = Regexp.escape(@template).gsub SCANNER do |match|
39
+ case match
40
+ when /\\\*$/
41
+ keys << match[1..-3]
42
+ "(.*?)"
43
+ else
44
+ keys << match[1..-1]
45
+ "([^/?&#]+)"
46
+ end
47
+ end
48
+
49
+ @parser = /\A#{pattern}\Z/
50
+
51
+ interpolation = @template.gsub('#', '\\#').gsub(SCANNER, '#{vals.shift}')
52
+
53
+ @materializer = eval(%{proc {|vals| "#{interpolation}" } })
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,87 @@
1
+ module Plato
2
+ class RenderContext
3
+ include Tilt::CompileSite
4
+
5
+ attr_reader :site, :document
6
+ alias post document
7
+
8
+ def initialize(site, document)
9
+ @site = site
10
+ @document = document
11
+ end
12
+
13
+ def template(key)
14
+ site.templates["#{key}.html"] || site.templates[key]
15
+ end
16
+
17
+ def render(template, locals = {}, &block)
18
+ template = self.template(template) if template.is_a? String
19
+ raise ArgumentError, "template #{template.inspect} not found" unless template
20
+ template.render(self, locals, &block)
21
+ end
22
+
23
+ def render_with_layout(template, format = nil, &block)
24
+ layout = template("_layout.#{format}") if format
25
+
26
+ if layout
27
+ render(layout) { render(template, &block) }
28
+ else
29
+ render(template, &block)
30
+ end
31
+ end
32
+
33
+ def render_body
34
+ document.body(self)
35
+ end
36
+ alias body render_body
37
+
38
+ def self.load_view_helpers(path)
39
+ return if @helpers_loaded
40
+ @helpers_loaded = true
41
+
42
+ mod = Module.new
43
+ mod.module_eval(File.read(path), path, 1)
44
+ include mod.const_get(mod.constants.first)
45
+ end
46
+
47
+ # base set of helpers
48
+
49
+ def url_for(doc, opts = {})
50
+ return doc if doc.is_a? String and doc =~ /\Ahttp:\/\//
51
+ base = opts[:absolute] ? site.base_url : '/'
52
+ File.join(base, doc.respond_to?(:path) ? doc.path : doc.to_s)
53
+ end
54
+
55
+ def link_to(title, url)
56
+ %{<a href="#{url}">#{title}</a>}
57
+ end
58
+
59
+ def content; site.content end
60
+
61
+ def attribute_pairs(hash)
62
+ hash.map {|k,v| %{#{k}="#{v}"} }.join(' ')
63
+ end
64
+
65
+ def css_include(url, opts = {})
66
+ url = "#{url.gsub(/\.css\Z/, '')}.css"
67
+ opts = opts.merge :type => "text/css", :rel => "stylesheet", :href=> url_for(url)
68
+ %{<link #{attribute_pairs(opts)} />}
69
+ end
70
+
71
+ def script_include(url, opts = {})
72
+ url = "#{url.gsub(/\.js\Z/, '')}.js"
73
+ opts = opts.merge :src => url_for(url)
74
+ %{<script #{attribute_pairs(opts)}></script>}
75
+ end
76
+ end
77
+
78
+ class RubyTiltTemplate < Tilt::Template
79
+ def prepare; end
80
+
81
+ def precompiled_template(locals)
82
+ data
83
+ end
84
+ end
85
+
86
+ ::Tilt.register('rb', RubyTiltTemplate)
87
+ end
data/lib/plato/repo.rb ADDED
@@ -0,0 +1,198 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ module Plato
5
+ class Repo
6
+ attr_reader :root, :codec
7
+
8
+ CODEC_MAP = {}
9
+
10
+ def initialize(root, codec = nil)
11
+ unless root == File.expand_path(root)
12
+ raise ArgumentError, "root is not an absolute path"
13
+ end
14
+
15
+ @codec = CODEC_MAP[codec] || StringCodec
16
+ @root = root.sub(/\/+\Z/, '') #remove trailing slash(es)
17
+ end
18
+
19
+ def load(path)
20
+ path = expanded_path(path)
21
+ if File.exist? path
22
+ @codec.read(path)
23
+ else
24
+ raise Filestore::NotFound
25
+ end
26
+ end
27
+
28
+ def save(path, data)
29
+ path = expanded_path(path)
30
+ FileUtils.mkdir_p(File.dirname(path))
31
+ @codec.write(path, data)
32
+ end
33
+
34
+ def destroy(path)
35
+ File.unlink expanded_path(path)
36
+
37
+ until (path = File.dirname(path)) == '.'
38
+ expanded = expanded_path(path)
39
+
40
+ if Dir[File.join(expanded, '*')].empty?
41
+ File.unlink expanded
42
+ else
43
+ return
44
+ end
45
+ end
46
+ end
47
+
48
+ def all
49
+ paths = Dir[File.join(root, '**/*')].select {|e| File.file? e }
50
+
51
+ if block_given?
52
+ paths = paths.select {|p| yield relative_path(p) }
53
+ end
54
+
55
+ paths.inject({}) do |hash, path|
56
+ hash.update relative_path(path) => load(path)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def expanded_path(path)
63
+ File.expand_path(path, root)
64
+ end
65
+
66
+ def relative_path(path)
67
+ path.sub(/\A#{Regexp.escape(root)}\//, '').tap do |relative|
68
+ raise ArgumentError, "path must subpath of root" if relative == path
69
+ end
70
+ end
71
+
72
+ module RefCodec
73
+ extend self
74
+
75
+ def write(path, ref)
76
+ FileUtils.cp(ref, path)
77
+ end
78
+
79
+ def read(ref)
80
+ ref
81
+ end
82
+ end
83
+ CODEC_MAP[:refs] = RefCodec
84
+
85
+ module StringCodec
86
+ extend self
87
+
88
+ def write(path, data)
89
+ File.open(path, 'w') {|f| f.write data }
90
+ end
91
+
92
+ def read(path)
93
+ File.read(path)
94
+ end
95
+ end
96
+ CODEC_MAP[:string] = StringCodec
97
+
98
+ module TemplateCodec
99
+ extend self
100
+
101
+ def read(path)
102
+ if template_class = Tilt[path]
103
+ template_class.new(path)
104
+ else
105
+ path
106
+ end
107
+ end
108
+
109
+ def write(path, data)
110
+ raise "Templates cannot be directly written"
111
+ end
112
+ end
113
+ CODEC_MAP[:template] = TemplateCodec
114
+
115
+ module HashCodec
116
+ extend StringCodec
117
+ extend self
118
+
119
+ def write(path, hash)
120
+ hash = stringify_keys(hash)
121
+ body = hash.delete('body')
122
+
123
+ data = [].tap do |buffer|
124
+ buffer << hash.map do |key, value|
125
+ "#{header_for(key)}: #{Sanitize.header(value)}"
126
+ end.join("\n")
127
+
128
+ buffer << "\n\n" << Sanitize.body(body) if body
129
+ buffer << "\n" unless buffer.last =~ /\n\Z/
130
+ end.join
131
+
132
+ super(path, data)
133
+ end
134
+
135
+ def read(path)
136
+ string = super
137
+
138
+ {}.tap do |result|
139
+ headers, body = string.split(/\n\n/, 2)
140
+
141
+ headers.split("\n").each do |line|
142
+ header, val = line.split(/:\s*/, 2)
143
+
144
+ result.update hash_key_for(header) => deserialize_value(val)
145
+ end
146
+
147
+ result['body'] = body.chomp if body
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def stringify_keys(hash)
154
+ hash.inject({}) {|h, (k, v)| h.update k.to_s => v }
155
+ end
156
+
157
+ def header_for(attr)
158
+ attr.to_s.gsub('_', ' ').gsub(/\b([a-z])/) {|m| m.capitalize }
159
+ end
160
+
161
+ def hash_key_for(header)
162
+ header.gsub(/\s+/, '_').downcase.to_s
163
+ end
164
+
165
+ def deserialize_value(val)
166
+ YAML.load(val) rescue val
167
+ end
168
+ end
169
+ CODEC_MAP[:hash] = HashCodec
170
+
171
+ module Sanitize
172
+ extend self
173
+
174
+ def header(header_val)
175
+ header_val.gsub(/(\r|\n)/) {|m| {"\r" => '\r', "\n" => '\n'}[m] }
176
+ end
177
+
178
+ def path_elem(elem)
179
+ elem.to_s.gsub(/\s+/, '_').gsub(/[^a-zA-Z0-9_-]/,'')
180
+ end
181
+
182
+ def body(body)
183
+ body # do we really need to do anything here?
184
+ end
185
+
186
+ def method_missing(method, value)
187
+ warn "Warning: not sanitizing #{method}."
188
+ value.to_s
189
+ end
190
+
191
+ private
192
+
193
+ def warn(warning)
194
+ $stderr.puts warning
195
+ end
196
+ end
197
+ end
198
+ end
data/lib/plato/site.rb ADDED
@@ -0,0 +1,120 @@
1
+ module Plato
2
+ class Site
3
+ attr_accessor :root
4
+
5
+ def initialize(root = '.')
6
+ @root = File.expand_path(root)
7
+ end
8
+
9
+ def generate!
10
+ RenderContext.load_view_helpers(File.join(template_path, 'view_helpers.rb'))
11
+ resources.save_to(cache_path)
12
+ template_resources.save_to(cache_path)
13
+ rendered_templates.save_to(cache_path)
14
+ rendered_content.save_to(cache_path)
15
+ end
16
+
17
+ def base_url; config['base_url'] end
18
+
19
+ def config_path; File.join(root, "config.rb") end
20
+ def template_path; File.join(root, "template") end
21
+ def content_path; File.join(root, "content") end
22
+ def resources_path; File.join(root, "resources") end
23
+ def cache_path; File.join(root, "cache") end
24
+
25
+ def config
26
+ @config ||= Config.read(ConfigDSL, File.read(config_path))
27
+ end
28
+
29
+ def templates
30
+ return @templates if @templates
31
+
32
+ manifest = Manifest.new template_path, {
33
+ :codec => :template,
34
+ :filter => lambda {|p| p !~ /\Aview_helpers\.rb/ }
35
+ }
36
+
37
+ path_parser = PathTemplate.new(":name*.:format.:engine")
38
+ sass_parser = PathTemplate.new(":name*.sass")
39
+
40
+ @template_resources = Manifest.new({}, :refs)
41
+ @templates = manifest.map do |path, template|
42
+ if template.is_a? String
43
+ # could not find a template engine, assume we're a raw resource
44
+ @template_resources[path] = template
45
+ nil
46
+ else
47
+ if match = path_parser.parse(path)
48
+ name, format = match.values_at("name", "format")
49
+ { "#{name}.#{format}" => Template.new(template, format) }
50
+ else name = sass_parser.parse(path).values_at("name")
51
+ { "#{name}.css" => Template.new(template, 'css') }
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def content
58
+ return @content if @content
59
+
60
+ @content = config["content_categories"]
61
+ categories = @content.values
62
+ manifest = Manifest.new(content_path, :hash)
63
+
64
+ manifest.each do |path, content_data|
65
+ if category = categories.find {|c| c.match path }
66
+ data = category.match(path).merge(content_data)
67
+
68
+ category.documents << Document.new(category, data)
69
+ end
70
+ end
71
+
72
+ @content
73
+ end
74
+
75
+ def template_resources
76
+ templates unless @templates
77
+ @template_resources
78
+ end
79
+
80
+ def resources
81
+ @resources ||= Manifest.new(resources_path, :refs)
82
+ end
83
+
84
+
85
+ # helpers
86
+
87
+ Template = Struct.new(:renderer, :format)
88
+
89
+ class Template
90
+ def method_missing(m, *a, &b); renderer.send(m, *a, &b) end
91
+ end
92
+
93
+ def render(template, format, document)
94
+ RenderContext.new(self, document).render_with_layout(template, format)
95
+ end
96
+
97
+ def rendered_templates
98
+ templates.map(:string) do |path, template|
99
+ if path =~ /\A_/
100
+ nil
101
+ else
102
+ { path => render(template, template.format, nil) }
103
+ end
104
+ end
105
+ end
106
+
107
+ def rendered_content
108
+ rendered = content.values.inject({}) do |hash, category|
109
+ if template = templates["_#{category.name}.html"]
110
+ category.documents.each do |document|
111
+ hash[document.path] = render(template, "html", document)
112
+ end
113
+ end
114
+ hash
115
+ end
116
+
117
+ Manifest.new(rendered)
118
+ end
119
+ end
120
+ end
data/lib/plato.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'tilt'
2
+
3
+ module Plato
4
+ require 'plato/config'
5
+ require 'plato/document'
6
+ require 'plato/manifest'
7
+ require 'plato/path_template'
8
+ require 'plato/repo'
9
+ require 'plato/rendering'
10
+ require 'plato/site'
11
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plato
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Matt Freels
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-10 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: tilt
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 21
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 1
34
+ version: 1.0.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rr
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ description: use templates and content to generate static sites.
66
+ email: matt@freels.name
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - .gitignore
75
+ - Rakefile
76
+ - VERSION
77
+ - lib/plato.rb
78
+ - lib/plato/config.rb
79
+ - lib/plato/document.rb
80
+ - lib/plato/manifest.rb
81
+ - lib/plato/path_template.rb
82
+ - lib/plato/rendering.rb
83
+ - lib/plato/repo.rb
84
+ - lib/plato/site.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/freels/plato
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options:
91
+ - --charset=UTF-8
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project:
115
+ rubygems_version: 1.3.7
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: An ideal static site generator
119
+ test_files: []
120
+