pliny 0.0.1.pre

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 (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