stormy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +65 -0
- data/README.md +64 -0
- data/Rakefile +4 -0
- data/lib/stormy.rb +34 -0
- data/lib/stormy/caches/base.rb +46 -0
- data/lib/stormy/caches/dummy_cache.rb +12 -0
- data/lib/stormy/caches/file_cache.rb +47 -0
- data/lib/stormy/caches/memory_cache.rb +24 -0
- data/lib/stormy/chunk.rb +63 -0
- data/lib/stormy/content.rb +21 -0
- data/lib/stormy/content_list.rb +18 -0
- data/lib/stormy/engines/base.rb +7 -0
- data/lib/stormy/engines/erb.rb +8 -0
- data/lib/stormy/engines/haml.rb +9 -0
- data/lib/stormy/engines/html.rb +6 -0
- data/lib/stormy/engines/markdown.rb +8 -0
- data/lib/stormy/engines/sass.rb +12 -0
- data/lib/stormy/layout.rb +21 -0
- data/lib/stormy/page.rb +32 -0
- data/lib/stormy/static.rb +20 -0
- data/lib/stormy/stores/base.rb +52 -0
- data/lib/stormy/stores/file_store.rb +70 -0
- data/lib/stormy/template.rb +67 -0
- data/lib/stormy/version.rb +3 -0
- data/lib/stormy_app.rb +40 -0
- data/lib/stormy_server.rb +41 -0
- data/spec/fixtures/dummy_site/config.yml +1 -0
- data/spec/fixtures/dummy_site/content/blog/blog-post-1.md +5 -0
- data/spec/fixtures/dummy_site/content/blog/blog-post-2.md +5 -0
- data/spec/fixtures/dummy_site/layouts/main.haml +2 -0
- data/spec/fixtures/dummy_site/public/assets/storm.jpg +0 -0
- data/spec/fixtures/dummy_site/public/blog.haml +12 -0
- data/spec/fixtures/dummy_site/public/blog_desc.haml +13 -0
- data/spec/fixtures/dummy_site/public/hardcoded_blog_post.haml +12 -0
- data/spec/fixtures/dummy_site/public/missing.haml +1 -0
- data/spec/fixtures/dummy_site/public/pathtest/:permalink.haml +16 -0
- data/spec/fixtures/dummy_site/public/redirecto.haml +1 -0
- data/spec/fixtures/dummy_site/public/redirects/:permalink.haml +1 -0
- data/spec/fixtures/dummy_site/public/subdir/index.haml +1 -0
- data/spec/fixtures/dummy_site/public/tester.haml +4 -0
- data/spec/fixtures/dummy_site/public/tester_invalid_layout.haml +4 -0
- data/spec/fixtures/dummy_site/tester.haml +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/stormy/caches/caches_spec.rb +50 -0
- data/spec/stormy/engines/erb_spec.rb +24 -0
- data/spec/stormy/engines/sass_spec.rb +24 -0
- data/spec/stormy/page_spec.rb +73 -0
- data/spec/stormy/stores/file_store_spec.rb +66 -0
- data/spec/stormy_app_spec.rb +37 -0
- data/spec/stormy_server_spec.rb +41 -0
- data/stormy.gemspec +33 -0
- data/tasks/rspec.rake +4 -0
- metadata +263 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
class Stormy::Layout < Stormy::Chunk
|
2
|
+
|
3
|
+
def render(child_template)
|
4
|
+
raise "No Matching Template: #{@key}" unless @template
|
5
|
+
output = child_template.render
|
6
|
+
@details.merge!(child_template.details)
|
7
|
+
@template.render do |section=nil|
|
8
|
+
output
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.fetch(app,key,params)
|
13
|
+
details = app.cache.layout(key) do
|
14
|
+
app.store.layout(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
self.new(app,details,params)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
data/lib/stormy/page.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Stormy::Page < Stormy::Chunk
|
2
|
+
|
3
|
+
def initialize(app, details, params)
|
4
|
+
|
5
|
+
super(app,details,params)
|
6
|
+
|
7
|
+
@layout = app.layout(@details["layout"],@details) if @details["layout"]
|
8
|
+
@template.content = resolve_content if details[:content] && @template
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.fetch(app,key,params)
|
12
|
+
details = app.cache.page(key) do
|
13
|
+
app.store.page(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
self.new(app,details,params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(status = nil)
|
20
|
+
output = @layout && mime_type == "text/html" ? @layout.render(@template) : @template.render
|
21
|
+
if details[:redirect]
|
22
|
+
[status || 301, {'Content-Type' => 'text','Location' => details[:redirect]}, ['301 found'] ]
|
23
|
+
else
|
24
|
+
[status || 200, {'Content-Type' => mime_type }, [ output ] ]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def mime_type
|
29
|
+
@mime_type ||= Rack::Mime.mime_type(File.extname(details["key"]),"text/html")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Stormy::Static
|
2
|
+
|
3
|
+
def initialize(root)
|
4
|
+
@root = root
|
5
|
+
@rack_file = Rack::File.new(root)
|
6
|
+
end
|
7
|
+
|
8
|
+
def can_serve?(path)
|
9
|
+
extension = Stormy::Template.extract_extension(path)
|
10
|
+
|
11
|
+
if !Stormy::Template.rendered_extension?(extension)
|
12
|
+
File.exists?(File.join(@root, path))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def serve(path)
|
17
|
+
@rack_file.call("PATH_INFO" => path, "REQUEST_METHOD"=>"GET")
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Stormy::Stores
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
# find the page that matches a key
|
12
|
+
def page(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def layout(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
# find the content that matches a category and a key
|
19
|
+
def content(category,key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# get all the content that matches a category
|
23
|
+
def content_list(category)
|
24
|
+
end
|
25
|
+
|
26
|
+
def extract_content(body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract(key,string, path_meta = {})
|
30
|
+
return {} unless string.present?
|
31
|
+
|
32
|
+
details = {}
|
33
|
+
if(string =~ /^(---\w*$\n.*?)^---\w*$\n(.*)/m)
|
34
|
+
begin
|
35
|
+
details = YAML.load($1).symbolize_keys
|
36
|
+
details[:body] = $2
|
37
|
+
rescue Exception => e
|
38
|
+
raise "Error Parsing YAML #{key}: #{e.to_s}"
|
39
|
+
end
|
40
|
+
else
|
41
|
+
details[:body] = string
|
42
|
+
end
|
43
|
+
|
44
|
+
details.merge!(path_meta)
|
45
|
+
details[:permalink] = Stormy::Template.extract_segment(key)
|
46
|
+
details[:key] = key
|
47
|
+
details
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Stormy::Stores::FileStore < Stormy::Stores::Base
|
2
|
+
|
3
|
+
def lookup_filename(base,path)
|
4
|
+
files = Dir.glob(@app.join(base,"#{path}.*"))
|
5
|
+
if valid_file?(files[0])
|
6
|
+
return files[0], {}
|
7
|
+
elsif File.directory?(@app.join(base,path))
|
8
|
+
files = Dir.glob(@app.join(base,path,"index.*"))
|
9
|
+
return files[0], {}
|
10
|
+
else
|
11
|
+
# split by separators and see if there we can match the pieces to segments
|
12
|
+
match_segments(base,path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout(key)
|
17
|
+
read_key("layouts",key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def page(key)
|
21
|
+
read_key("public",key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def content(category,key)
|
25
|
+
read_key("content/#{category}",key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def content_list(category, options)
|
29
|
+
base = @app.join("content",category)
|
30
|
+
files = Dir.glob(File.join(base,"*.*"))
|
31
|
+
if options[:order]
|
32
|
+
files = files.sort
|
33
|
+
files = files.reverse if options[:desc]
|
34
|
+
end
|
35
|
+
files.map { |file| read_file(file, File.basename(file)) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_key(base,path)
|
39
|
+
file, path_meta = lookup_filename(base,path)
|
40
|
+
return {} unless file
|
41
|
+
read_file(file,path,path_meta)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_file(file, path, path_meta = {})
|
45
|
+
fp = File.open(file,"rt:UTF-8")
|
46
|
+
path_meta[:path] = file
|
47
|
+
extract(path, fp.read, path_meta)
|
48
|
+
end
|
49
|
+
|
50
|
+
def match_segments(base,path)
|
51
|
+
path_pieces = path.split("/").reject(&:blank?)
|
52
|
+
permalink_piece = path_pieces.pop
|
53
|
+
partial_path = path_pieces.join("/")
|
54
|
+
if File.directory?(@app.join(base,partial_path))
|
55
|
+
files = Dir.glob(@app.join(base,partial_path,":*.*"))
|
56
|
+
if valid_file?(files[0])
|
57
|
+
extension = File.extname(files[0])
|
58
|
+
keyname = File.basename(files[0], extension)[1..-1]
|
59
|
+
return files[0], { keyname.to_sym => permalink_piece }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def valid_file?(filename)
|
67
|
+
filename.present? && !File.directory?(filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Stormy::Template
|
2
|
+
|
3
|
+
attr_reader :details
|
4
|
+
attr_accessor :content
|
5
|
+
|
6
|
+
def initialize(app,key,details)
|
7
|
+
@app = app
|
8
|
+
@details = details
|
9
|
+
@key = key
|
10
|
+
@body = details["body"]
|
11
|
+
|
12
|
+
@engine = resolve_engine(self.class.extract_extension(@key))
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(&block)
|
16
|
+
if @engine
|
17
|
+
@engine.render(@body,resolve_bindings,&block)
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
@@engines = {
|
26
|
+
"md" => "Markdown",
|
27
|
+
"haml" => "Haml",
|
28
|
+
"txt" => "Text",
|
29
|
+
"html" => "Html",
|
30
|
+
"erb" => "Erb",
|
31
|
+
"scss" => "Sass"
|
32
|
+
}
|
33
|
+
|
34
|
+
@@supported_extensions = @@engines.keys
|
35
|
+
|
36
|
+
def self.rendered_extension?(extension)
|
37
|
+
@@supported_extensions.include?(extension)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.extract_segment(path)
|
41
|
+
extension = extract_extension(path)
|
42
|
+
if rendered_extension?(extension)
|
43
|
+
File.basename(path,"." + extension)
|
44
|
+
else
|
45
|
+
File.basename(path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.extract_extension(key)
|
50
|
+
return "txt" unless key.include?(".")
|
51
|
+
key.split(".")[-1].to_s.downcase
|
52
|
+
end
|
53
|
+
|
54
|
+
def resolve_engine(extension)
|
55
|
+
engine = @@engines[extension]
|
56
|
+
if !engine.nil?
|
57
|
+
"Stormy::Engines::#{engine}".constantize.new(@app)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def resolve_bindings
|
62
|
+
{
|
63
|
+
meta: @details
|
64
|
+
}.merge(@content || {})
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/stormy_app.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class StormyApp
|
2
|
+
|
3
|
+
attr_reader :root, :cache, :store, :defaults, :options, :page_not_found
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@root = options[:root] || raise("Missing :root config")
|
8
|
+
@cache = (options[:cache] || Stormy::Caches::DummyCache).new(self)
|
9
|
+
@store = (options[:store] || Stormy::Stores::FileStore).new(self)
|
10
|
+
@page_not_found = (options[:page_not_found] || "/404")
|
11
|
+
@defaults = YAML.load_file(File.join(root, options[:defaults] || 'config.yml')) rescue {}
|
12
|
+
@defaults.symbolize_keys!
|
13
|
+
end
|
14
|
+
|
15
|
+
def page(path,params={})
|
16
|
+
Stormy::Page.fetch(self,path,params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def layout(path,params={})
|
20
|
+
Stormy::Layout.fetch(self,path,params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def content(category,key)
|
24
|
+
Stormy::Content.fetch(self,category,key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def content_list(category,options={})
|
28
|
+
Stormy::ContentList.fetch(self,category,options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def template(key,details)
|
32
|
+
Stormy::Template.new(self,key,details)
|
33
|
+
end
|
34
|
+
|
35
|
+
def join(*args)
|
36
|
+
sanitized_args = [ self.root ] + args.map { |arg| arg.gsub("..","") }
|
37
|
+
File.join(*sanitized_args)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "rack/mime"
|
3
|
+
|
4
|
+
class StormyServer
|
5
|
+
def initialize(config_options)
|
6
|
+
@app = StormyApp.new(config_options)
|
7
|
+
@file_server = Stormy::Static.new(File.join(@app.root,"public"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
render(::Rack::Utils.unescape(env['PATH_INFO']))
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def render(path)
|
16
|
+
if @file_server.can_serve?(path)
|
17
|
+
@file_server.serve(path)
|
18
|
+
else
|
19
|
+
output = render_page(path)
|
20
|
+
if output
|
21
|
+
output
|
22
|
+
else
|
23
|
+
[404, {"Content-Type" => "text/html"}, [ "Page Not Found" ] ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_page(path)
|
29
|
+
@page = @app.page(path,{ "path" => path })
|
30
|
+
|
31
|
+
if @page.valid?
|
32
|
+
@page.render
|
33
|
+
else
|
34
|
+
@error_page = @app.page(@app.page_not_found, { "path" => @app.page_not_found })
|
35
|
+
if @error_page.valid?
|
36
|
+
@error_page.render(404)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
layout: main
|
Binary file
|