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.
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
+