stormy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +65 -0
  6. data/README.md +64 -0
  7. data/Rakefile +4 -0
  8. data/lib/stormy.rb +34 -0
  9. data/lib/stormy/caches/base.rb +46 -0
  10. data/lib/stormy/caches/dummy_cache.rb +12 -0
  11. data/lib/stormy/caches/file_cache.rb +47 -0
  12. data/lib/stormy/caches/memory_cache.rb +24 -0
  13. data/lib/stormy/chunk.rb +63 -0
  14. data/lib/stormy/content.rb +21 -0
  15. data/lib/stormy/content_list.rb +18 -0
  16. data/lib/stormy/engines/base.rb +7 -0
  17. data/lib/stormy/engines/erb.rb +8 -0
  18. data/lib/stormy/engines/haml.rb +9 -0
  19. data/lib/stormy/engines/html.rb +6 -0
  20. data/lib/stormy/engines/markdown.rb +8 -0
  21. data/lib/stormy/engines/sass.rb +12 -0
  22. data/lib/stormy/layout.rb +21 -0
  23. data/lib/stormy/page.rb +32 -0
  24. data/lib/stormy/static.rb +20 -0
  25. data/lib/stormy/stores/base.rb +52 -0
  26. data/lib/stormy/stores/file_store.rb +70 -0
  27. data/lib/stormy/template.rb +67 -0
  28. data/lib/stormy/version.rb +3 -0
  29. data/lib/stormy_app.rb +40 -0
  30. data/lib/stormy_server.rb +41 -0
  31. data/spec/fixtures/dummy_site/config.yml +1 -0
  32. data/spec/fixtures/dummy_site/content/blog/blog-post-1.md +5 -0
  33. data/spec/fixtures/dummy_site/content/blog/blog-post-2.md +5 -0
  34. data/spec/fixtures/dummy_site/layouts/main.haml +2 -0
  35. data/spec/fixtures/dummy_site/public/assets/storm.jpg +0 -0
  36. data/spec/fixtures/dummy_site/public/blog.haml +12 -0
  37. data/spec/fixtures/dummy_site/public/blog_desc.haml +13 -0
  38. data/spec/fixtures/dummy_site/public/hardcoded_blog_post.haml +12 -0
  39. data/spec/fixtures/dummy_site/public/missing.haml +1 -0
  40. data/spec/fixtures/dummy_site/public/pathtest/:permalink.haml +16 -0
  41. data/spec/fixtures/dummy_site/public/redirecto.haml +1 -0
  42. data/spec/fixtures/dummy_site/public/redirects/:permalink.haml +1 -0
  43. data/spec/fixtures/dummy_site/public/subdir/index.haml +1 -0
  44. data/spec/fixtures/dummy_site/public/tester.haml +4 -0
  45. data/spec/fixtures/dummy_site/public/tester_invalid_layout.haml +4 -0
  46. data/spec/fixtures/dummy_site/tester.haml +1 -0
  47. data/spec/spec_helper.rb +14 -0
  48. data/spec/stormy/caches/caches_spec.rb +50 -0
  49. data/spec/stormy/engines/erb_spec.rb +24 -0
  50. data/spec/stormy/engines/sass_spec.rb +24 -0
  51. data/spec/stormy/page_spec.rb +73 -0
  52. data/spec/stormy/stores/file_store_spec.rb +66 -0
  53. data/spec/stormy_app_spec.rb +37 -0
  54. data/spec/stormy_server_spec.rb +41 -0
  55. data/stormy.gemspec +33 -0
  56. data/tasks/rspec.rake +4 -0
  57. metadata +263 -0
@@ -0,0 +1,8 @@
1
+ class Stormy::Engines::Erb < Stormy::Engines::Base
2
+
3
+ def render(body,bindings)
4
+ engine = ::ERB.new(body, 0, "<>")
5
+ engine.result OpenStruct.new(bindings).instance_eval { binding }
6
+ end
7
+
8
+ end
@@ -0,0 +1,9 @@
1
+ require "haml"
2
+
3
+ class Stormy::Engines::Haml < Stormy::Engines::Base
4
+
5
+ def render(body,bindings,&block)
6
+ engine = Haml::Engine.new(body)
7
+ engine.render(Object.new, bindings,&block)
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class Stormy::Engines::Html< Stormy::Engines::Base
2
+
3
+ def render(body,bindings,&block)
4
+ body
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ require "kramdown"
2
+
3
+ class Stormy::Engines::Markdown < Stormy::Engines::Base
4
+
5
+ def render(body,bindings,&block)
6
+ Kramdown::Document.new(body).to_html
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require "sass"
2
+
3
+ class Stormy::Engines::Sass < Stormy::Engines::Base
4
+
5
+ def render(body,bindings,&block)
6
+ engine = Sass::Engine.new(body,
7
+ syntax: :scss,
8
+ load_paths: [ @app.join("public/assets") ]
9
+ )
10
+ engine.render
11
+ end
12
+ end
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Stormy
2
+ VERSION="0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Super post
3
+ author: Svend Karlson
4
+ ---
5
+ This is the content of the blog post yo! This is the content of the blog post yo! This is the content of the blog post yo!
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Super post 2
3
+ author: Bob Johnson
4
+ ---
5
+ This is the content of the second blog post.
@@ -0,0 +1,2 @@
1
+ %h1 Title
2
+ = yield
@@ -0,0 +1,12 @@
1
+ ---
2
+ content:
3
+ type: blog
4
+ order: true
5
+ ---
6
+ %ul.posts
7
+ - blog.each do |post|
8
+ %li.post
9
+ %h2= post[:title]
10
+ %h3= post[:author]
11
+ = post[:body]
12
+