pliny 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/bin/pliny-generate +6 -0
  3. data/lib/pliny.rb +21 -0
  4. data/lib/pliny/commands/generator.rb +197 -0
  5. data/lib/pliny/config_helpers.rb +24 -0
  6. data/lib/pliny/errors.rb +109 -0
  7. data/lib/pliny/extensions/instruments.rb +38 -0
  8. data/lib/pliny/log.rb +84 -0
  9. data/lib/pliny/middleware/cors.rb +46 -0
  10. data/lib/pliny/middleware/request_id.rb +42 -0
  11. data/lib/pliny/middleware/request_store.rb +14 -0
  12. data/lib/pliny/middleware/rescue_errors.rb +24 -0
  13. data/lib/pliny/middleware/timeout.rb +25 -0
  14. data/lib/pliny/middleware/versioning.rb +64 -0
  15. data/lib/pliny/request_store.rb +22 -0
  16. data/lib/pliny/router.rb +15 -0
  17. data/lib/pliny/tasks.rb +3 -0
  18. data/lib/pliny/tasks/db.rake +116 -0
  19. data/lib/pliny/tasks/test.rake +8 -0
  20. data/lib/pliny/templates/endpoint.erb +30 -0
  21. data/lib/pliny/templates/endpoint_acceptance_test.erb +40 -0
  22. data/lib/pliny/templates/endpoint_scaffold.erb +49 -0
  23. data/lib/pliny/templates/endpoint_scaffold_acceptance_test.erb +55 -0
  24. data/lib/pliny/templates/endpoint_test.erb +16 -0
  25. data/lib/pliny/templates/mediator.erb +22 -0
  26. data/lib/pliny/templates/mediator_test.erb +5 -0
  27. data/lib/pliny/templates/migration.erb +9 -0
  28. data/lib/pliny/templates/model.erb +5 -0
  29. data/lib/pliny/templates/model_migration.erb +10 -0
  30. data/lib/pliny/templates/model_test.erb +5 -0
  31. data/lib/pliny/utils.rb +31 -0
  32. data/lib/pliny/version.rb +3 -0
  33. data/test/commands/generator_test.rb +147 -0
  34. data/test/errors_test.rb +24 -0
  35. data/test/extensions/instruments_test.rb +34 -0
  36. data/test/log_test.rb +27 -0
  37. data/test/middleware/cors_test.rb +42 -0
  38. data/test/middleware/request_id_test.rb +28 -0
  39. data/test/middleware/request_store_test.rb +25 -0
  40. data/test/middleware/rescue_errors_test.rb +41 -0
  41. data/test/middleware/timeout_test.rb +32 -0
  42. data/test/middleware/versioning_test.rb +63 -0
  43. data/test/request_store_test.rb +25 -0
  44. data/test/router_test.rb +39 -0
  45. data/test/test_helper.rb +18 -0
  46. metadata +252 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 18bf67cc7da9438b4f46b8a0265edbfb4253484f
4
+ data.tar.gz: 09a5892f943e2aedea7fda831a85924212902deb
5
+ SHA512:
6
+ metadata.gz: b415fc1bb35256ee85dd80bde4bf4fcdfb1995839646c59cfb1cc162bed23b76cbbc0e1cf7bb9155b85971156747c4b3c238a3b900d8f8ccc923c1b1d46a272d
7
+ data.tar.gz: 55aa879eec76133b9c01e979a92a332e99ea1a7af61ec3eab861488a3187aee134b7875271d9ddb91248618a060dac70afcce77847ab7688256e33decea4263d
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ Bundler.require
5
+
6
+ Pliny::Commands::Generator.run(ARGV.dup)
data/lib/pliny.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "multi_json"
2
+ require "sinatra/base"
3
+
4
+ require "pliny/version"
5
+ require "pliny/commands/generator"
6
+ require "pliny/errors"
7
+ require "pliny/extensions/instruments"
8
+ require "pliny/log"
9
+ require "pliny/request_store"
10
+ require "pliny/router"
11
+ require "pliny/utils"
12
+ require "pliny/middleware/cors"
13
+ require "pliny/middleware/request_id"
14
+ require "pliny/middleware/request_store"
15
+ require "pliny/middleware/rescue_errors"
16
+ require "pliny/middleware/timeout"
17
+ require "pliny/middleware/versioning"
18
+
19
+ module Pliny
20
+ extend Log
21
+ end
@@ -0,0 +1,197 @@
1
+ require "erb"
2
+ require "fileutils"
3
+ require "ostruct"
4
+ require "active_support/inflector"
5
+ require "prmd"
6
+
7
+ module Pliny::Commands
8
+ class Generator
9
+ attr_accessor :args, :stream
10
+
11
+ def self.run(args, stream=$stdout)
12
+ new(args).run!
13
+ end
14
+
15
+ def initialize(args={}, stream=$stdout)
16
+ @args = args
17
+ @stream = stream
18
+ end
19
+
20
+ def run!
21
+ unless type
22
+ raise "Missing type of object to generate"
23
+ end
24
+ unless name
25
+ raise "Missing #{type} name"
26
+ end
27
+
28
+ case type
29
+ when "endpoint"
30
+ create_endpoint(scaffold: false)
31
+ create_endpoint_test
32
+ create_endpoint_acceptance_test(scaffold: false)
33
+ when "mediator"
34
+ create_mediator
35
+ create_mediator_test
36
+ when "migration"
37
+ create_migration
38
+ when "model"
39
+ create_model
40
+ create_model_migration
41
+ create_model_test
42
+ when "scaffold"
43
+ create_endpoint(scaffold: true)
44
+ create_endpoint_test
45
+ create_endpoint_acceptance_test(scaffold: true)
46
+ create_model
47
+ create_model_migration
48
+ create_model_test
49
+ create_schema
50
+ rebuild_schema
51
+ when "schema"
52
+ create_schema
53
+ rebuild_schema
54
+ else
55
+ abort("Don't know how to generate '#{type}'.")
56
+ end
57
+ end
58
+
59
+ def type
60
+ args.first
61
+ end
62
+
63
+ def name
64
+ args[1]
65
+ end
66
+
67
+ def singular_class_name
68
+ name.singularize.camelize
69
+ end
70
+
71
+ def plural_class_name
72
+ name.pluralize.camelize
73
+ end
74
+
75
+ def field_name
76
+ name.tableize.singularize
77
+ end
78
+
79
+ def table_name
80
+ name.tableize
81
+ end
82
+
83
+ def display(msg)
84
+ stream.puts msg
85
+ end
86
+
87
+ def create_endpoint(options = {})
88
+ endpoint = "./lib/endpoints/#{name.pluralize}.rb"
89
+ template = options[:scaffold] ? "endpoint_scaffold.erb" : "endpoint.erb"
90
+ render_template(template, endpoint, {
91
+ plural_class_name: plural_class_name,
92
+ singular_class_name: singular_class_name,
93
+ field_name: field_name,
94
+ url_path: url_path,
95
+ })
96
+ display "created endpoint file #{endpoint}"
97
+ display "add the following to lib/routes.rb:"
98
+ display " mount Endpoints::#{plural_class_name}"
99
+ end
100
+
101
+ def create_endpoint_test
102
+ test = "./spec/endpoints/#{name.pluralize}_spec.rb"
103
+ render_template("endpoint_test.erb", test, {
104
+ plural_class_name: plural_class_name,
105
+ singular_class_name: singular_class_name,
106
+ url_path: url_path,
107
+ })
108
+ display "created test #{test}"
109
+ end
110
+
111
+ def create_endpoint_acceptance_test(options = {})
112
+ test = "./spec/acceptance/#{name.pluralize}_spec.rb"
113
+ template = options[:scaffold] ? "endpoint_scaffold_acceptance_test.erb" :
114
+ "endpoint_acceptance_test.erb"
115
+ render_template(template, test, {
116
+ plural_class_name: plural_class_name,
117
+ field_name: field_name,
118
+ singular_class_name: singular_class_name,
119
+ url_path: url_path,
120
+ })
121
+ display "created test #{test}"
122
+ end
123
+
124
+ def create_mediator
125
+ mediator = "./lib/mediators/#{name}.rb"
126
+ render_template("mediator.erb", mediator, plural_class_name: plural_class_name)
127
+ display "created mediator file #{mediator}"
128
+ end
129
+
130
+ def create_mediator_test
131
+ test = "./spec/mediators/#{name}_spec.rb"
132
+ render_template("mediator_test.erb", test, plural_class_name: plural_class_name)
133
+ display "created test #{test}"
134
+ end
135
+
136
+ def create_migration
137
+ migration = "./db/migrate/#{Time.now.to_i}_#{name}.rb"
138
+ render_template("migration.erb", migration)
139
+ display "created migration #{migration}"
140
+ end
141
+
142
+ def create_model
143
+ model = "./lib/models/#{name}.rb"
144
+ render_template("model.erb", model, singular_class_name: singular_class_name)
145
+ display "created model file #{model}"
146
+ end
147
+
148
+ def create_model_migration
149
+ migration = "./db/migrate/#{Time.now.to_i}_create_#{table_name}.rb"
150
+ render_template("model_migration.erb", migration,
151
+ table_name: table_name)
152
+ display "created migration #{migration}"
153
+ end
154
+
155
+ def create_model_test
156
+ test = "./spec/models/#{name}_spec.rb"
157
+ render_template("model_test.erb", test, singular_class_name: singular_class_name)
158
+ display "created test #{test}"
159
+ end
160
+
161
+ def create_schema
162
+ schema = "./docs/schema/schemata/#{name.singularize}.yaml"
163
+ write_file(schema) do
164
+ Prmd.init(name.singularize, yaml: true)
165
+ end
166
+ display "created schema file #{schema}"
167
+ end
168
+
169
+ def rebuild_schema
170
+ schemata = "./docs/schema.json"
171
+ write_file(schemata) do
172
+ Prmd.combine("./docs/schema/schemata", meta: "./docs/schema/meta.json")
173
+ end
174
+ display "rebuilt #{schemata}"
175
+ end
176
+
177
+ def render_template(template_file, destination_path, vars={})
178
+ template_path = File.dirname(__FILE__) + "/../templates/#{template_file}"
179
+ template = ERB.new(File.read(template_path), 0, ">")
180
+ context = OpenStruct.new(vars)
181
+ write_file(destination_path) do
182
+ template.result(context.instance_eval { binding })
183
+ end
184
+ end
185
+
186
+ def url_path
187
+ "/" + name.pluralize.gsub(/_/, '-')
188
+ end
189
+
190
+ def write_file(destination_path)
191
+ FileUtils.mkdir_p(File.dirname(destination_path))
192
+ File.open(destination_path, "w") do |f|
193
+ f.puts yield
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,24 @@
1
+ module Pliny
2
+ module ConfigHelpers
3
+ def optional(*attrs)
4
+ attrs.each do |attr|
5
+ instance_eval "def #{attr}; @#{attr} ||= ENV['#{attr.upcase}'] end", __FILE__, __LINE__
6
+ end
7
+ end
8
+
9
+ def mandatory(*attrs)
10
+ attrs.each do |attr|
11
+ instance_eval "def #{attr}; @#{attr} ||= ENV['#{attr.upcase}'] || raise('missing=#{attr.upcase}') end", __FILE__, __LINE__
12
+ end
13
+ end
14
+
15
+ def override(attrs)
16
+ attrs.each do |attr, value|
17
+ instance_eval "def #{attr}; @#{attr} ||= ENV['#{attr.upcase}'] || '#{value}'.to_s end", __FILE__, __LINE__
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Supress the "use RbConfig instead" warning.
24
+ Object.send :remove_const, :Config
@@ -0,0 +1,109 @@
1
+ module Pliny
2
+ module Errors
3
+ class Error < StandardError
4
+ attr_accessor :id
5
+
6
+ def initialize(message, id)
7
+ @id = id
8
+ super(message)
9
+ end
10
+ end
11
+
12
+ class HTTPStatusError < Error
13
+ attr_reader :status
14
+
15
+ def initialize(message = nil, id = nil, status = nil)
16
+ meta = Pliny::Errors::META[self.class]
17
+ message = message || meta[1] + "."
18
+ id = id || meta[1].downcase.gsub(/ /, '_').to_sym
19
+ @status = status || meta[0]
20
+ super(message, id)
21
+ end
22
+ end
23
+
24
+ class Continue < HTTPStatusError; end # 100
25
+ class SwitchingProtocols < HTTPStatusError; end # 101
26
+ class OK < HTTPStatusError; end # 200
27
+ class Created < HTTPStatusError; end # 201
28
+ class Accepted < HTTPStatusError; end # 202
29
+ class NonAuthoritativeInformation < HTTPStatusError; end # 203
30
+ class NoContent < HTTPStatusError; end # 204
31
+ class ResetContent < HTTPStatusError; end # 205
32
+ class PartialContent < HTTPStatusError; end # 206
33
+ class MultipleChoices < HTTPStatusError; end # 300
34
+ class MovedPermanently < HTTPStatusError; end # 301
35
+ class Found < HTTPStatusError; end # 302
36
+ class SeeOther < HTTPStatusError; end # 303
37
+ class NotModified < HTTPStatusError; end # 304
38
+ class UseProxy < HTTPStatusError; end # 305
39
+ class TemporaryRedirect < HTTPStatusError; end # 307
40
+ class BadRequest < HTTPStatusError; end # 400
41
+ class Unauthorized < HTTPStatusError; end # 401
42
+ class PaymentRequired < HTTPStatusError; end # 402
43
+ class Forbidden < HTTPStatusError; end # 403
44
+ class NotFound < HTTPStatusError; end # 404
45
+ class MethodNotAllowed < HTTPStatusError; end # 405
46
+ class NotAcceptable < HTTPStatusError; end # 406
47
+ class ProxyAuthenticationRequired < HTTPStatusError; end # 407
48
+ class RequestTimeout < HTTPStatusError; end # 408
49
+ class Conflict < HTTPStatusError; end # 409
50
+ class Gone < HTTPStatusError; end # 410
51
+ class LengthRequired < HTTPStatusError; end # 411
52
+ class PreconditionFailed < HTTPStatusError; end # 412
53
+ class RequestEntityTooLarge < HTTPStatusError; end # 413
54
+ class RequestURITooLong < HTTPStatusError; end # 414
55
+ class UnsupportedMediaType < HTTPStatusError; end # 415
56
+ class RequestedRangeNotSatisfiable < HTTPStatusError; end # 416
57
+ class ExpectationFailed < HTTPStatusError; end # 417
58
+ class UnprocessableEntity < HTTPStatusError; end # 422
59
+ class InternalServerError < HTTPStatusError; end # 500
60
+ class NotImplemented < HTTPStatusError; end # 501
61
+ class BadGateway < HTTPStatusError; end # 502
62
+ class ServiceUnavailable < HTTPStatusError; end # 503
63
+ class GatewayTimeout < HTTPStatusError; end # 504
64
+
65
+ # Messages for nicer exceptions, from rfc2616
66
+ META = {
67
+ Continue => [100, 'Continue'],
68
+ SwitchingProtocols => [101, 'Switching protocols'],
69
+ OK => [200, 'OK'],
70
+ Created => [201, 'Created'],
71
+ Accepted => [202, 'Accepted'],
72
+ NonAuthoritativeInformation => [203, 'Non-authoritative information'],
73
+ NoContent => [204, 'No content'],
74
+ ResetContent => [205, 'Reset content'],
75
+ PartialContent => [206, 'Partial content'],
76
+ MultipleChoices => [300, 'Multiple choices'],
77
+ MovedPermanently => [301, 'Moved permanently'],
78
+ Found => [302, 'Found'],
79
+ SeeOther => [303, 'See other'],
80
+ NotModified => [304, 'Not modified'],
81
+ UseProxy => [305, 'Use proxy'],
82
+ TemporaryRedirect => [307, 'Temporary redirect'],
83
+ BadRequest => [400, 'Bad request'],
84
+ Unauthorized => [401, 'Unauthorized'],
85
+ PaymentRequired => [402, 'Payment required'],
86
+ Forbidden => [403, 'Forbidden'],
87
+ NotFound => [404, 'Not found'],
88
+ MethodNotAllowed => [405, 'Method not allowed'],
89
+ NotAcceptable => [406, 'Not acceptable'],
90
+ ProxyAuthenticationRequired => [407, 'Proxy authentication required'],
91
+ RequestTimeout => [408, 'Request timeout'],
92
+ Conflict => [409, 'Conflict'],
93
+ Gone => [410, 'Gone'],
94
+ LengthRequired => [411, 'Length required'],
95
+ PreconditionFailed => [412, 'Precondition failed'],
96
+ RequestEntityTooLarge => [413, 'Request entity too large'],
97
+ RequestURITooLong => [414, 'Request-URI too long'],
98
+ UnsupportedMediaType => [415, 'Unsupported media type'],
99
+ RequestedRangeNotSatisfiable => [416, 'Requested range not satisfiable'],
100
+ ExpectationFailed => [417, 'Expectation failed'],
101
+ UnprocessableEntity => [422, 'Unprocessable entity'],
102
+ InternalServerError => [500, 'Internal server error'],
103
+ NotImplemented => [501, 'Not implemented'],
104
+ BadGateway => [502, 'Bad gateway'],
105
+ ServiceUnavailable => [503, 'Service unavailable'],
106
+ GatewayTimeout => [504, 'Gateway timeout'],
107
+ }.freeze
108
+ end
109
+ end
@@ -0,0 +1,38 @@
1
+ module Pliny::Extensions
2
+ module Instruments
3
+ def self.registered(app)
4
+ app.before do
5
+ @request_start = Time.now
6
+ Pliny.log(
7
+ instrumentation: true,
8
+ at: "start",
9
+ method: request.request_method,
10
+ path: request.path_info,
11
+ )
12
+ end
13
+
14
+ app.after do
15
+ Pliny.log(
16
+ instrumentation: true,
17
+ at: "finish",
18
+ method: request.request_method,
19
+ path: request.path_info,
20
+ route_signature: route_signature,
21
+ status: status,
22
+ elapsed: (Time.now - @request_start).to_f
23
+ )
24
+ end
25
+
26
+ app.helpers do
27
+ def route_signature
28
+ env["ROUTE_SIGNATURE"]
29
+ end
30
+ end
31
+ end
32
+
33
+ def route(verb, path, *)
34
+ condition { env["ROUTE_SIGNATURE"] = path.to_s }
35
+ super
36
+ end
37
+ end
38
+ end
data/lib/pliny/log.rb ADDED
@@ -0,0 +1,84 @@
1
+ module Pliny
2
+ module Log
3
+ def log(data, &block)
4
+ data = log_context.merge(data)
5
+ log_to_stream(stdout || $stdout, data, &block)
6
+ end
7
+
8
+ def stdout=(stream)
9
+ @stdout = stream
10
+ end
11
+
12
+ def stdout
13
+ @stdout
14
+ end
15
+
16
+ private
17
+
18
+ def log_context
19
+ RequestStore.store[:log_context] || {}
20
+ end
21
+
22
+ def log_to_stream(stream, data, &block)
23
+ unless block
24
+ str = unparse(data)
25
+ if RUBY_PLATFORM == "java"
26
+ stream.puts str
27
+ else
28
+ mtx.synchronize { stream.puts str }
29
+ end
30
+ else
31
+ data = data.dup
32
+ start = Time.now
33
+ log_to_stream(stream, data.merge(at: "start"))
34
+ begin
35
+ res = yield
36
+ log_to_stream(stream, data.merge(
37
+ at: "finish", elapsed: (Time.now - start).to_f))
38
+ res
39
+ rescue
40
+ log_to_stream(stream, data.merge(
41
+ at: "exception", elapsed: (Time.now - start).to_f))
42
+ raise
43
+ end
44
+ end
45
+ end
46
+
47
+ def mtx
48
+ @mtx ||= Mutex.new
49
+ end
50
+
51
+ def quote_string(k, v)
52
+ # try to find a quote style that fits
53
+ if !v.include?('"')
54
+ %{#{k}="#{v}"}
55
+ elsif !v.include?("'")
56
+ %{#{k}='#{v}'}
57
+ else
58
+ %{#{k}="#{v.gsub(/"/, '\\"')}"}
59
+ end
60
+ end
61
+
62
+ def unparse(attrs)
63
+ attrs.map { |k, v| unparse_pair(k, v) }.compact.join(" ")
64
+ end
65
+
66
+ def unparse_pair(k, v)
67
+ v = v.call if v.is_a?(Proc)
68
+ # only quote strings if they include whitespace
69
+ if v == nil
70
+ nil
71
+ elsif v == true
72
+ k
73
+ elsif v.is_a?(Float)
74
+ "#{k}=#{format("%.3f", v)}"
75
+ elsif v.is_a?(String) && v =~ /\s/
76
+ quote_string(k, v)
77
+ elsif v.is_a?(Time)
78
+ "#{k}=#{v.iso8601}"
79
+ else
80
+ "#{k}=#{v}"
81
+ end
82
+ end
83
+ end
84
+ end