stormy 0.0.1
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.
- 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
|