plato 0.0.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/.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
+