edison 0.0.2 → 0.0.3
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/README.md +59 -0
- data/bin/edison +7 -201
- data/lib/edison.rb +222 -0
- metadata +5 -4
data/README.md
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Edison
|
2
|
+
|
3
|
+
Jekyll is nice, but doesn't give you much flexibility. Everything is a post! What if you want a static site with multiple types of objects?
|
4
|
+
|
5
|
+
Enter Edison.
|
6
|
+
|
7
|
+
```
|
8
|
+
$ cat <<END > _config.rb
|
9
|
+
Edison.config do
|
10
|
+
models.posts.each do |post|
|
11
|
+
Edison::Helpers.date_from_filename(post)
|
12
|
+
post.url = "posts/#{post._fname}"
|
13
|
+
routes.url File.join(post.url, "index.html"), "post", post
|
14
|
+
end
|
15
|
+
|
16
|
+
routes.url "index.html" do |data|
|
17
|
+
data.posts = models.posts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
END
|
21
|
+
|
22
|
+
$ mkdir -p _models/posts
|
23
|
+
$ cat <<END > _models/posts/$(date +%Y-%m-%d)-my-first-post.md
|
24
|
+
---
|
25
|
+
title: My first post!
|
26
|
+
---
|
27
|
+
I'm blogging on [Edison](http://github.com/michaelmaltese/edison)!
|
28
|
+
END
|
29
|
+
|
30
|
+
$ mkdir _views
|
31
|
+
$ cat <<END > _views/post.html
|
32
|
+
<h1>{{ title }}</h1>
|
33
|
+
<em>{{ date }}</em>
|
34
|
+
{{{ body }}}
|
35
|
+
END
|
36
|
+
|
37
|
+
$ cat <<END > index.html
|
38
|
+
<h1>An Edison Blog</h1>
|
39
|
+
<ul>
|
40
|
+
{{# posts}}
|
41
|
+
<li><a href="{{ url }}">{{ title }}</a></li>
|
42
|
+
{{/ posts}}
|
43
|
+
</ul>
|
44
|
+
END
|
45
|
+
|
46
|
+
$ edison serve
|
47
|
+
Running in /Users/michaelmaltese/edison-example...
|
48
|
+
Loading model posts/2013-03-17-my-first-post.md...
|
49
|
+
Loading view _views/post.html...
|
50
|
+
Copying static files...
|
51
|
+
Creating /index.html...
|
52
|
+
Creating /posts/2013-03-17-my-first-post/index.html...
|
53
|
+
Done!
|
54
|
+
[2013-03-17 17:36:47] INFO WEBrick 1.3.1
|
55
|
+
[2013-03-17 17:36:47] INFO ruby 1.8.7 (2012-02-08) [universal-darwin11.0]
|
56
|
+
[2013-03-17 17:36:48] INFO WEBrick::HTTPServer#start: pid=28835 port=4000
|
57
|
+
```
|
58
|
+
|
59
|
+
Mustache, Markdown, and YAML keep you nice and clean.
|
data/bin/edison
CHANGED
@@ -1,206 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'commander/import'
|
4
|
+
require 'edison'
|
4
5
|
require 'listen'
|
5
|
-
require 'kramdown'
|
6
|
-
require 'hashie'
|
7
|
-
require 'mustache'
|
8
6
|
require 'pp'
|
9
7
|
require 'webrick'
|
10
|
-
require 'yaml'
|
11
|
-
|
12
|
-
module Edison
|
13
|
-
end
|
14
|
-
|
15
|
-
module Edison::YAMLFrontMatter
|
16
|
-
def self.read(fname)
|
17
|
-
contents = File.read(fname).strip
|
18
|
-
if contents =~ /\A---/
|
19
|
-
_, yaml, body = contents.split("---", 3)
|
20
|
-
yaml = YAML.load(yaml)
|
21
|
-
body ||= ""
|
22
|
-
body.strip!
|
23
|
-
if yaml.include? "body"
|
24
|
-
raise Exception, "YAML Front Matter can't contain a 'body' key. Put it after the front matter."
|
25
|
-
end
|
26
|
-
{"body" => body}.merge(yaml)
|
27
|
-
else
|
28
|
-
{"body" => contents}
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class Edison::ModelsLoader
|
34
|
-
def load(directory)
|
35
|
-
klasses = Dir[File.join(directory, "*")].map &File.method(:basename)
|
36
|
-
hash = Hashie::Mash.new
|
37
|
-
klasses.each do |klass|
|
38
|
-
objects = Dir[File.join(directory, klass, "*")]
|
39
|
-
objects.map! do |fname|
|
40
|
-
puts "Loading model #{File.join(klass, File.basename(fname))}..."
|
41
|
-
|
42
|
-
data = Hashie::Mash.new(case File.extname(fname)
|
43
|
-
when /(html)|(md)$/
|
44
|
-
Edison::YAMLFrontMatter.read(fname)
|
45
|
-
when /ya?ml$/
|
46
|
-
YAML.load_file(fname)
|
47
|
-
else
|
48
|
-
{}
|
49
|
-
end)
|
50
|
-
|
51
|
-
if data._ext
|
52
|
-
raise Exception, "Restricted key '_ext' appears in model!"
|
53
|
-
end
|
54
|
-
data._ext = File.extname(fname)
|
55
|
-
|
56
|
-
if data._ext == ".md"
|
57
|
-
data.body = Kramdown::Document.new(data.body).to_html
|
58
|
-
end
|
59
|
-
|
60
|
-
if data._fname
|
61
|
-
raise Exception, "Restricted key '_fname' appears in model!"
|
62
|
-
end
|
63
|
-
data._fname = File.basename(fname).sub(/#{File.extname(fname)}$/,'')
|
64
|
-
|
65
|
-
data
|
66
|
-
end
|
67
|
-
hash[klass] = objects
|
68
|
-
end
|
69
|
-
hash
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class Edison::Renderer
|
74
|
-
attr_accessor :views
|
75
|
-
def initialize(views)
|
76
|
-
self.views = views
|
77
|
-
end
|
78
|
-
def render(view_name, data)
|
79
|
-
view = self.views[view_name]
|
80
|
-
if view.nil?
|
81
|
-
raise Exception, "Called for view #{view_name}, but _views/#{view_name} does not exist"
|
82
|
-
end
|
83
|
-
body = Mustache.render(view.body, data)
|
84
|
-
if view.layout
|
85
|
-
newdata = data.merge("yield" => body)
|
86
|
-
self.render(view.layout, newdata)
|
87
|
-
else
|
88
|
-
body
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
class Edison::Router
|
94
|
-
attr_reader :urls, :static
|
95
|
-
def initialize
|
96
|
-
@urls = []
|
97
|
-
@static = []
|
98
|
-
end
|
99
|
-
def url(url, view=nil, data={}, &b)
|
100
|
-
if b and view and data
|
101
|
-
raise Exception, "Please pass data or block, not both!"
|
102
|
-
end
|
103
|
-
if b
|
104
|
-
b.call(@urls.find { |(url, _, _)| url == url}[2])
|
105
|
-
else
|
106
|
-
@urls << [url, view, Hashie::Mash.new(data)]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
def copy(url)
|
110
|
-
@static << url
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
module Edison
|
115
|
-
class <<self
|
116
|
-
attr_reader :models, :routes, :renderer
|
117
|
-
end
|
118
|
-
def self.initialize!(directory)
|
119
|
-
@directory = directory
|
120
|
-
|
121
|
-
@routes = Router.new
|
122
|
-
|
123
|
-
loader = ModelsLoader.new
|
124
|
-
@models = loader.load(File.join(directory, "_models"))
|
125
|
-
|
126
|
-
views = Hash[Dir[File.join(directory, "_views", "*")].map do |fname|
|
127
|
-
puts "Loading view _views/#{File.basename(fname)}..."
|
128
|
-
name = File.basename(fname).sub(/\.[^\.]+$/,'')
|
129
|
-
data = Hashie::Mash.new Edison::YAMLFrontMatter.read(fname)
|
130
|
-
[name, data]
|
131
|
-
end]
|
132
|
-
|
133
|
-
static = Dir[File.join(directory, "**/*")]
|
134
|
-
blacklist = ["Gemfile", "Gemfile.lock", "config.rb", "gen.rb", "serve.rb"]
|
135
|
-
static.reject! do |fname|
|
136
|
-
name = fname.sub(/^#{directory}\//,'')
|
137
|
-
dirname = File.dirname(fname)
|
138
|
-
blacklist.include?(name) or name =~ /^_/ or File.directory?(fname)
|
139
|
-
end
|
140
|
-
static.each do |fname|
|
141
|
-
name = fname.sub(/^#{directory}\//,'')
|
142
|
-
if %w{.html .md}.include?(File.extname(fname))
|
143
|
-
data = Hashie::Mash.new Edison::YAMLFrontMatter.read(fname)
|
144
|
-
url = name.sub(/\.md$/,'')
|
145
|
-
views[url] = data
|
146
|
-
@routes.url url, url, data
|
147
|
-
else
|
148
|
-
@routes.copy name
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
@renderer = Renderer.new(views)
|
153
|
-
end
|
154
|
-
def self.config(&b)
|
155
|
-
instance_eval &b
|
156
|
-
end
|
157
|
-
def self.generate!
|
158
|
-
site = File.join(@directory, "_site")
|
159
|
-
|
160
|
-
FileUtils.rm_rf(site)
|
161
|
-
Dir.mkdir(site)
|
162
|
-
puts "Copying static files..."
|
163
|
-
routes.static.each do |name|
|
164
|
-
src = File.join(@directory, name)
|
165
|
-
dest = File.join(site, name)
|
166
|
-
FileUtils.mkdir_p File.dirname(dest)
|
167
|
-
FileUtils.copy src, dest
|
168
|
-
end
|
169
|
-
routes.urls.each do |(url, view_name, data)|
|
170
|
-
puts "Creating /#{url}..."
|
171
|
-
|
172
|
-
fname = File.join(site, url)
|
173
|
-
FileUtils.mkdir_p(File.dirname(fname))
|
174
|
-
File.open(fname, "w") do |f|
|
175
|
-
f.write @renderer.render(view_name, data)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
module Edison::Helpers
|
182
|
-
def self.date_from_filename(data)
|
183
|
-
if data.date
|
184
|
-
raise Exception, "Date will be inferred from filename, but found in data"
|
185
|
-
end
|
186
|
-
|
187
|
-
if File.basename(data._fname) =~ /^(\d{4}-\d{1,2}-\d{1,2})/
|
188
|
-
data.date = Date.parse($1)
|
189
|
-
else
|
190
|
-
raise Exception, "Expected filename to start with date (YYYY-M?M-D?D)"
|
191
|
-
end
|
192
|
-
|
193
|
-
data
|
194
|
-
end
|
195
|
-
|
196
|
-
def self.default_layout(data)
|
197
|
-
unless data.layout
|
198
|
-
data.layout = "default"
|
199
|
-
end
|
200
|
-
|
201
|
-
data
|
202
|
-
end
|
203
|
-
end
|
204
8
|
|
205
9
|
def build
|
206
10
|
directory = Dir.pwd
|
@@ -212,10 +16,10 @@ def build
|
|
212
16
|
puts "Done!"
|
213
17
|
end
|
214
18
|
|
215
|
-
|
19
|
+
puts "Edison #{Edison::VERSION} on Ruby #{RUBY_VERSION}"
|
216
20
|
|
217
21
|
program :name, "Edison"
|
218
|
-
program :version,
|
22
|
+
program :version, Edison::VERSION
|
219
23
|
program :description, "Static website generator"
|
220
24
|
|
221
25
|
default_command :help
|
@@ -251,12 +55,14 @@ command :serve do |c|
|
|
251
55
|
pp e
|
252
56
|
end
|
253
57
|
end
|
254
|
-
listener.start(false)
|
255
58
|
|
256
59
|
Signal.trap("INT") do
|
257
60
|
server.shutdown
|
258
61
|
listener.stop
|
62
|
+
exit
|
63
|
+
Kernel.abort
|
259
64
|
end
|
65
|
+
listener.start(false)
|
260
66
|
server.start
|
261
67
|
end
|
262
68
|
end
|
data/lib/edison.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'kramdown'
|
3
|
+
require 'hashie'
|
4
|
+
require 'mustache'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module Edison
|
9
|
+
VERSION = "0.0.3"
|
10
|
+
end
|
11
|
+
|
12
|
+
module Edison::YAMLFrontMatter
|
13
|
+
def self.read(fname)
|
14
|
+
contents = File.read(fname).strip
|
15
|
+
if contents =~ /\A---/
|
16
|
+
_, yaml, body = contents.split("---", 3)
|
17
|
+
yaml = YAML.load(yaml)
|
18
|
+
body ||= ""
|
19
|
+
body.strip!
|
20
|
+
if yaml.include? "body"
|
21
|
+
raise Exception, "YAML Front Matter can't contain a 'body' key. Put it after the front matter."
|
22
|
+
end
|
23
|
+
{"body" => body}.merge(yaml)
|
24
|
+
else
|
25
|
+
{"body" => contents}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Edison::ModelsLoader
|
31
|
+
def load(directory)
|
32
|
+
klasses = Dir[File.join(directory, "*")].map &File.method(:basename)
|
33
|
+
hash = Hashie::Mash.new
|
34
|
+
klasses.each do |klass|
|
35
|
+
objects = Dir[File.join(directory, klass, "*")]
|
36
|
+
objects.map! do |fname|
|
37
|
+
puts "Loading model #{File.join(klass, File.basename(fname))}..."
|
38
|
+
|
39
|
+
data = Hashie::Mash.new(case File.extname(fname)
|
40
|
+
when /(html)|(md)$/
|
41
|
+
Edison::YAMLFrontMatter.read(fname)
|
42
|
+
when /ya?ml$/
|
43
|
+
YAML.load_file(fname)
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end)
|
47
|
+
|
48
|
+
if data._ext
|
49
|
+
raise Exception, "Restricted key '_ext' appears in model!"
|
50
|
+
end
|
51
|
+
data._ext = File.extname(fname)
|
52
|
+
|
53
|
+
if data._ext == ".md"
|
54
|
+
data.body = Kramdown::Document.new(data.body).to_html
|
55
|
+
end
|
56
|
+
|
57
|
+
if data._fname
|
58
|
+
raise Exception, "Restricted key '_fname' appears in model!"
|
59
|
+
end
|
60
|
+
data._fname = File.basename(fname).sub(/#{File.extname(fname)}$/,'')
|
61
|
+
|
62
|
+
data
|
63
|
+
end
|
64
|
+
hash[klass] = objects
|
65
|
+
end
|
66
|
+
hash
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Edison::Renderer
|
71
|
+
attr_accessor :views
|
72
|
+
def initialize(views)
|
73
|
+
self.views = views
|
74
|
+
end
|
75
|
+
def render(view_name, data)
|
76
|
+
view = self.views[view_name]
|
77
|
+
if view.nil?
|
78
|
+
raise Exception, "Called for view #{view_name}, but _views/#{view_name} does not exist"
|
79
|
+
end
|
80
|
+
body = Mustache.render(view.body, data)
|
81
|
+
if view.layout
|
82
|
+
newdata = data.merge("yield" => body)
|
83
|
+
self.render(view.layout, newdata)
|
84
|
+
else
|
85
|
+
body
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Edison::Router
|
91
|
+
attr_reader :urls, :static
|
92
|
+
def initialize
|
93
|
+
@urls = []
|
94
|
+
@static = []
|
95
|
+
end
|
96
|
+
def url(url, view=nil, data={}, &b)
|
97
|
+
if b and view and data
|
98
|
+
raise Exception, "Please pass data or block, not both!"
|
99
|
+
end
|
100
|
+
if b
|
101
|
+
data = @urls.find { |(u, _, _)| u == url}[2]
|
102
|
+
b.call(data)
|
103
|
+
else
|
104
|
+
@urls << [url, view, Hashie::Mash.new(data)]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
def copy(url)
|
108
|
+
@static << url
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Edison::StaticFileHandler
|
113
|
+
def self.filter_blacklisted(directory, files)
|
114
|
+
blacklist = ["Gemfile", "Gemfile.lock", "config.rb", "Rakefile", "Procfile", "Makefile"]
|
115
|
+
files.reject do |fname|
|
116
|
+
name = fname.sub(/^#{directory}\//,'')
|
117
|
+
dirname = File.dirname(fname)
|
118
|
+
blacklist.include?(name) or name =~ /^_/ or File.directory?(fname)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
def self.load(directory)
|
122
|
+
static = Dir[File.join(directory, "**/*")]
|
123
|
+
static = self.filter_blacklisted(directory, static)
|
124
|
+
|
125
|
+
possibly_dynamic = []
|
126
|
+
just_copy = []
|
127
|
+
|
128
|
+
static.each do |fname|
|
129
|
+
name = fname.sub(/^#{directory}\//,'')
|
130
|
+
if %w{.html .md}.include?(File.extname(fname))
|
131
|
+
data = Hashie::Mash.new Edison::YAMLFrontMatter.read(fname)
|
132
|
+
url = name.sub(/\.md$/,'')
|
133
|
+
possibly_dynamic << [url, data]
|
134
|
+
else
|
135
|
+
just_copy << name
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
OpenStruct.new(
|
140
|
+
:possibly_dynamic => possibly_dynamic,
|
141
|
+
:just_copy => just_copy
|
142
|
+
)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
module Edison
|
147
|
+
class <<self
|
148
|
+
attr_reader :models, :routes, :renderer
|
149
|
+
end
|
150
|
+
def self.initialize!(directory)
|
151
|
+
@directory = directory
|
152
|
+
|
153
|
+
@routes = Router.new
|
154
|
+
|
155
|
+
loader = ModelsLoader.new
|
156
|
+
@models = loader.load(File.join(directory, "_models"))
|
157
|
+
|
158
|
+
@views = Hash[Dir[File.join(directory, "_views", "*")].map do |fname|
|
159
|
+
puts "Loading view _views/#{File.basename(fname)}..."
|
160
|
+
name = File.basename(fname).sub(/\.[^\.]+$/,'')
|
161
|
+
data = Hashie::Mash.new Edison::YAMLFrontMatter.read(fname)
|
162
|
+
[name, data]
|
163
|
+
end]
|
164
|
+
|
165
|
+
static = StaticFileHandler.load(directory)
|
166
|
+
static.possibly_dynamic.each do |(url, data)|
|
167
|
+
@routes.url url, url, data
|
168
|
+
@views[url] = data
|
169
|
+
end
|
170
|
+
static.just_copy.each &@routes.method(:copy)
|
171
|
+
end
|
172
|
+
def self.config(&b)
|
173
|
+
instance_eval &b
|
174
|
+
end
|
175
|
+
def self.generate!
|
176
|
+
renderer = Renderer.new(@views)
|
177
|
+
site = File.join(@directory, "_site")
|
178
|
+
|
179
|
+
FileUtils.rm_rf(site)
|
180
|
+
Dir.mkdir(site)
|
181
|
+
puts "Copying static files..."
|
182
|
+
routes.static.each do |name|
|
183
|
+
src = File.join(@directory, name)
|
184
|
+
dest = File.join(site, name)
|
185
|
+
FileUtils.mkdir_p File.dirname(dest)
|
186
|
+
FileUtils.copy src, dest
|
187
|
+
end
|
188
|
+
routes.urls.each do |(url, view_name, data)|
|
189
|
+
puts "Creating /#{url}..."
|
190
|
+
|
191
|
+
fname = File.join(site, url)
|
192
|
+
FileUtils.mkdir_p(File.dirname(fname))
|
193
|
+
File.open(fname, "w") do |f|
|
194
|
+
f.write renderer.render(view_name, data)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
module Edison::Helpers
|
201
|
+
def self.date_from_filename(data)
|
202
|
+
if data.date
|
203
|
+
raise Exception, "Date will be inferred from filename, but found in data"
|
204
|
+
end
|
205
|
+
|
206
|
+
if File.basename(data._fname) =~ /^(\d{4}-\d{1,2}-\d{1,2})/
|
207
|
+
data.date = Date.parse($1)
|
208
|
+
else
|
209
|
+
raise Exception, "Expected filename to start with date (YYYY-M?M-D?D)"
|
210
|
+
end
|
211
|
+
|
212
|
+
data
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.default_layout(data)
|
216
|
+
unless data.layout
|
217
|
+
data.layout = "default"
|
218
|
+
end
|
219
|
+
|
220
|
+
data
|
221
|
+
end
|
222
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: edison
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Michael Maltese
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2013-03-
|
18
|
+
date: 2013-03-23 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: commander
|
@@ -96,6 +96,7 @@ extensions: []
|
|
96
96
|
extra_rdoc_files: []
|
97
97
|
|
98
98
|
files:
|
99
|
+
- lib/edison.rb
|
99
100
|
- bin/edison
|
100
101
|
- README.md
|
101
102
|
homepage: http://github.com/michaelmaltese/edison
|