goofy 1.0.2

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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gems +4 -0
  3. data/.gitignore +2 -0
  4. data/CHANGELOG +47 -0
  5. data/CONTRIBUTING +19 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +23 -0
  8. data/README.md +67 -0
  9. data/app/.rspec +2 -0
  10. data/app/Gemfile +13 -0
  11. data/app/app/controllers/application_controller.rb +3 -0
  12. data/app/app/services/.keep +0 -0
  13. data/app/config.ru +4 -0
  14. data/app/config/environment.rb +9 -0
  15. data/app/config/initializers/.keep +0 -0
  16. data/app/config/routes.rb +7 -0
  17. data/app/config/settings.rb +0 -0
  18. data/app/spec/helpers.rb +2 -0
  19. data/app/spec/helpers/goofy.rb +11 -0
  20. data/app/spec/spec_helper.rb +107 -0
  21. data/benchmark/measure.rb +35 -0
  22. data/bin/check_its_goofy.rb +15 -0
  23. data/bin/goofy +61 -0
  24. data/bin/goofy_generator.rb +357 -0
  25. data/bin/goofy_instance_creator.rb +40 -0
  26. data/examples/config.ru +18 -0
  27. data/examples/measure.rb +17 -0
  28. data/examples/rack-response.ru +21 -0
  29. data/examples/views/home.mote +7 -0
  30. data/examples/views/layout.mote +11 -0
  31. data/goofy.gemspec +26 -0
  32. data/lib/goofy.rb +405 -0
  33. data/lib/goofy/capybara.rb +13 -0
  34. data/lib/goofy/controller.rb +14 -0
  35. data/lib/goofy/controller/base.rb +21 -0
  36. data/lib/goofy/controller/callbacks.rb +19 -0
  37. data/lib/goofy/render.rb +63 -0
  38. data/lib/goofy/router.rb +9 -0
  39. data/lib/goofy/safe.rb +23 -0
  40. data/lib/goofy/safe/csrf.rb +47 -0
  41. data/lib/goofy/safe/secure_headers.rb +40 -0
  42. data/lib/goofy/test.rb +11 -0
  43. data/makefile +4 -0
  44. data/test/accept.rb +32 -0
  45. data/test/captures.rb +162 -0
  46. data/test/composition.rb +69 -0
  47. data/test/controller.rb +29 -0
  48. data/test/cookie.rb +34 -0
  49. data/test/csrf.rb +139 -0
  50. data/test/extension.rb +21 -0
  51. data/test/helper.rb +11 -0
  52. data/test/host.rb +29 -0
  53. data/test/integration.rb +114 -0
  54. data/test/match.rb +86 -0
  55. data/test/middleware.rb +46 -0
  56. data/test/number.rb +36 -0
  57. data/test/on.rb +157 -0
  58. data/test/param.rb +66 -0
  59. data/test/path.rb +86 -0
  60. data/test/plugin.rb +68 -0
  61. data/test/rack.rb +22 -0
  62. data/test/redirect.rb +21 -0
  63. data/test/render.rb +128 -0
  64. data/test/root.rb +83 -0
  65. data/test/run.rb +23 -0
  66. data/test/safe.rb +74 -0
  67. data/test/segment.rb +45 -0
  68. data/test/session.rb +21 -0
  69. data/test/settings.rb +52 -0
  70. data/test/views/about.erb +1 -0
  71. data/test/views/about.str +1 -0
  72. data/test/views/content-yield.erb +1 -0
  73. data/test/views/custom/abs_path.mote +1 -0
  74. data/test/views/frag.mote +1 -0
  75. data/test/views/home.erb +2 -0
  76. data/test/views/home.mote +1 -0
  77. data/test/views/home.str +2 -0
  78. data/test/views/layout-alternative.erb +2 -0
  79. data/test/views/layout-yield.erb +3 -0
  80. data/test/views/layout.erb +2 -0
  81. data/test/views/layout.mote +2 -0
  82. data/test/views/layout.str +2 -0
  83. data/test/views/test.erb +1 -0
  84. data/test/with.rb +42 -0
  85. metadata +271 -0
@@ -0,0 +1,13 @@
1
+ require "goofy"
2
+ require "cutest"
3
+ require "capybara/dsl"
4
+
5
+ class Cutest::Scope
6
+ if defined? Capybara::DSL
7
+ include Capybara::DSL
8
+ else
9
+ include Capybara
10
+ end
11
+ end
12
+
13
+ Capybara.app = Goofy
@@ -0,0 +1,14 @@
1
+ require_relative "controller/callbacks"
2
+ require_relative "controller/base"
3
+ require_relative "router"
4
+
5
+ # include router functionality into Goofy
6
+ Goofy.include Goofy::Router
7
+
8
+ class Goofy
9
+
10
+ # Define Controller Class
11
+ class Controller
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ class Goofy
2
+ class Controller
3
+
4
+ def self.construct(arg)
5
+ self.new(arg).entry
6
+ end
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def response
13
+ res.write "<h3>You should define `response` instance method on your controller class!</h3>"
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ @app.send(name, *args, &block)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'prong'
2
+
3
+ class Goofy
4
+ class Controller
5
+
6
+ include Prong
7
+
8
+ # Define before, around, after callbacks for #response
9
+ define_hook :response
10
+
11
+ def entry
12
+ run_hooks :response do
13
+ # Call response method with running callbacks
14
+ response
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ require "tilt"
2
+
3
+ class Goofy
4
+ module Render
5
+ def self.setup(app)
6
+ app.settings[:render] ||= {}
7
+ app.settings[:render][:template_engine] ||= "erb"
8
+ app.settings[:render][:layout] ||= "layout"
9
+ app.settings[:render][:views] ||= File.expand_path("views", Dir.pwd)
10
+ app.settings[:render][:options] ||= {
11
+ default_encoding: Encoding.default_external
12
+ }
13
+ end
14
+
15
+ def render(template, locals = {}, layout = settings[:render][:layout])
16
+ res.headers["Content-Type"] ||= "text/html; charset=utf-8"
17
+ res.write(view(template, locals, layout))
18
+ end
19
+
20
+ def view(template, locals = {}, layout = settings[:render][:layout])
21
+ partial(layout, locals.merge(content: partial(template, locals)))
22
+ end
23
+
24
+ def partial(template, locals = {})
25
+ _render(template_path(template), locals, settings[:render][:options])
26
+ end
27
+
28
+ def template_path(template)
29
+ dir = settings[:render][:views]
30
+ ext = settings[:render][:template_engine]
31
+
32
+ return File.join(dir, "#{ template }.#{ ext }")
33
+ end
34
+
35
+ # @private Renders any type of template file supported by Tilt.
36
+ #
37
+ # @example
38
+ #
39
+ # # Renders home, and is assumed to be HAML.
40
+ # _render("home.haml")
41
+ #
42
+ # # Renders with some local variables
43
+ # _render("home.haml", site_name: "My Site")
44
+ #
45
+ # # Renders with HAML options
46
+ # _render("home.haml", {}, ugly: true, format: :html5)
47
+ #
48
+ # # Renders in layout
49
+ # _render("layout.haml") { _render("home.haml") }
50
+ #
51
+ def _render(template, locals = {}, options = {}, &block)
52
+ _cache.fetch(template) {
53
+ Tilt.new(template, 1, options.merge(outvar: '@_output'))
54
+ }.render(self, locals, &block)
55
+ end
56
+
57
+ # @private Used internally by #_render to cache the
58
+ # Tilt templates.
59
+ def _cache
60
+ Thread.current[:_cache] ||= Tilt::Cache.new
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ class Goofy
2
+ module Router
3
+
4
+ def controller(ctrl_class)
5
+ ctrl_class.construct self
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "safe/csrf"
2
+ require_relative "safe/secure_headers"
3
+
4
+ class Goofy
5
+ # == Goofy::Safe
6
+ #
7
+ # This plugin contains security related features for Goofy
8
+ # applications. It takes ideas from secureheaders[1].
9
+ #
10
+ # == Usage
11
+ #
12
+ # require "goofy"
13
+ # require "goofy/safe"
14
+ #
15
+ # Goofy.plugin(Goofy::Safe)
16
+ #
17
+ module Safe
18
+ def self.setup(app)
19
+ app.plugin(Safe::SecureHeaders)
20
+ app.plugin(Safe::CSRF)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ class Goofy
2
+ module Safe
3
+ module CSRF
4
+ def csrf
5
+ @csrf ||= Goofy::Safe::CSRF::Helper.new(req)
6
+ end
7
+
8
+ class Helper
9
+ attr :req
10
+
11
+ def initialize(req)
12
+ @req = req
13
+ end
14
+
15
+ def token
16
+ session[:csrf_token] ||= SecureRandom.base64(32)
17
+ end
18
+
19
+ def reset!
20
+ session.delete(:csrf_token)
21
+ end
22
+
23
+ def safe?
24
+ return req.get? || req.head? ||
25
+ req[:csrf_token] == token ||
26
+ req.env["HTTP_X_CSRF_TOKEN"] == token
27
+ end
28
+
29
+ def unsafe?
30
+ return !safe?
31
+ end
32
+
33
+ def form_tag
34
+ return %Q(<input type="hidden" name="csrf_token" value="#{ token }">)
35
+ end
36
+
37
+ def meta_tag
38
+ return %Q(<meta name="csrf_token" content="#{ token }">)
39
+ end
40
+
41
+ def session
42
+ return req.env["rack.session"]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ # == Secure HTTP Headers
2
+ #
3
+ # This plugin will automatically apply several headers that are
4
+ # related to security. This includes:
5
+ #
6
+ # - HTTP Strict Transport Security (HSTS) [2].
7
+ # - X-Frame-Options [3].
8
+ # - X-XSS-Protection [4].
9
+ # - X-Content-Type-Options [5].
10
+ # - X-Download-Options [6].
11
+ # - X-Permitted-Cross-Domain-Policies [7].
12
+ #
13
+ # == References
14
+ #
15
+ # [1]: https://github.com/twitter/secureheaders
16
+ # [2]: https://tools.ietf.org/html/rfc6797
17
+ # [3]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
18
+ # [4]: http://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx
19
+ # [5]: http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
20
+ # [6]: http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx
21
+ # [7]: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
22
+ #
23
+ class Goofy
24
+ module Safe
25
+ module SecureHeaders
26
+ HEADERS = {
27
+ "X-Content-Type-Options" => "nosniff",
28
+ "X-Download-Options" => "noopen",
29
+ "X-Frame-Options" => "SAMEORIGIN",
30
+ "X-Permitted-Cross-Domain-Policies" => "none",
31
+ "X-XSS-Protection" => "1; mode=block",
32
+ "Strict-Transport-Security" => "max-age=2628000"
33
+ }
34
+
35
+ def self.setup(app)
36
+ app.settings[:default_headers].merge!(HEADERS)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ require "goofy"
2
+ require "cutest"
3
+ require "rack/test"
4
+
5
+ class Cutest::Scope
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ Goofy
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest ./test/*.rb
@@ -0,0 +1,32 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "accept mimetypes" do
4
+ Goofy.define do
5
+ on accept("application/xml") do
6
+ res.write res["Content-Type"]
7
+ end
8
+ end
9
+
10
+ env = { "HTTP_ACCEPT" => "application/xml",
11
+ "SCRIPT_NAME" => "/", "PATH_INFO" => "/post" }
12
+
13
+ _, _, body = Goofy.call(env)
14
+
15
+ assert_response body, ["application/xml"]
16
+ end
17
+
18
+ test "tests don't fail when you don't specify an accept type" do
19
+ Goofy.define do
20
+ on accept("application/xml") do
21
+ res.write res["Content-Type"]
22
+ end
23
+
24
+ on default do
25
+ res.write "Default action"
26
+ end
27
+ end
28
+
29
+ _, _, body = Goofy.call({})
30
+
31
+ assert_response body, ["Default action"]
32
+ end
@@ -0,0 +1,162 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "doesn't yield HOST" do
4
+ Goofy.define do
5
+ on host("example.com") do |*args|
6
+ res.write args.size
7
+ end
8
+ end
9
+
10
+ env = { "HTTP_HOST" => "example.com" }
11
+
12
+ _, _, resp = Goofy.call(env)
13
+
14
+ assert_response resp, ["0"]
15
+ end
16
+
17
+ test "doesn't yield the verb" do
18
+ Goofy.define do
19
+ on get do |*args|
20
+ res.write args.size
21
+ end
22
+ end
23
+
24
+ env = { "REQUEST_METHOD" => "GET" }
25
+
26
+ _, _, resp = Goofy.call(env)
27
+
28
+ assert_response resp, ["0"]
29
+ end
30
+
31
+ test "doesn't yield the path" do
32
+ Goofy.define do
33
+ on get, "home" do |*args|
34
+ res.write args.size
35
+ end
36
+ end
37
+
38
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/home",
39
+ "SCRIPT_NAME" => "/" }
40
+
41
+ _, _, resp = Goofy.call(env)
42
+
43
+ assert_response resp, ["0"]
44
+ end
45
+
46
+ test "yields the segment" do
47
+ Goofy.define do
48
+ on get, "user", :id do |id|
49
+ res.write id
50
+ end
51
+ end
52
+
53
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/johndoe",
54
+ "SCRIPT_NAME" => "/" }
55
+
56
+ _, _, resp = Goofy.call(env)
57
+
58
+ assert_response resp, ["johndoe"]
59
+ end
60
+
61
+ test "yields a number" do
62
+ Goofy.define do
63
+ on get, "user", :id do |id|
64
+ res.write id
65
+ end
66
+ end
67
+
68
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/101",
69
+ "SCRIPT_NAME" => "/" }
70
+
71
+ _, _, resp = Goofy.call(env)
72
+
73
+ assert_response resp, ["101"]
74
+ end
75
+
76
+ test "yield a file name with a matching extension" do
77
+ Goofy.define do
78
+ on get, "css", extension("css") do |file|
79
+ res.write file
80
+ end
81
+ end
82
+
83
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/css/app.css",
84
+ "SCRIPT_NAME" => "/" }
85
+
86
+ _, _, resp = Goofy.call(env)
87
+
88
+ assert_response resp, ["app"]
89
+ end
90
+
91
+ test "yields a segment per nested block" do
92
+ Goofy.define do
93
+ on :one do |one|
94
+ on :two do |two|
95
+ on :three do |three|
96
+ res.write one
97
+ res.write two
98
+ res.write three
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/one/two/three",
105
+ "SCRIPT_NAME" => "/" }
106
+
107
+ _, _, resp = Goofy.call(env)
108
+
109
+ assert_response resp, ["one", "two", "three"]
110
+ end
111
+
112
+ test "consumes a slash if needed" do
113
+ Goofy.define do
114
+ on get, "(.+\\.css)" do |file|
115
+ res.write file
116
+ end
117
+ end
118
+
119
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/foo/bar.css",
120
+ "SCRIPT_NAME" => "/" }
121
+
122
+ _, _, resp = Goofy.call(env)
123
+
124
+ assert_response resp, ["foo/bar.css"]
125
+ end
126
+
127
+ test "regex captures in string format" do
128
+ Goofy.define do
129
+ on get, "posts/(\\d+)-(.*)" do |id, slug|
130
+ res.write id
131
+ res.write slug
132
+ end
133
+ end
134
+
135
+
136
+ env = { "REQUEST_METHOD" => "GET",
137
+ "PATH_INFO" => "/posts/123-postal-service",
138
+ "SCRIPT_NAME" => "/" }
139
+
140
+ _, _, resp = Goofy.call(env)
141
+
142
+
143
+ assert_response resp, ["123", "postal-service"]
144
+ end
145
+
146
+ test "regex captures in regex format" do
147
+ Goofy.define do
148
+ on get, %r{posts/(\d+)-(.*)} do |id, slug|
149
+ res.write id
150
+ res.write slug
151
+ end
152
+ end
153
+
154
+ env = { "REQUEST_METHOD" => "GET",
155
+ "PATH_INFO" => "/posts/123-postal-service",
156
+ "SCRIPT_NAME" => "/" }
157
+
158
+ _, _, resp = Goofy.call(env)
159
+
160
+
161
+ assert_response resp, ["123", "postal-service"]
162
+ end