punk 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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/Gemfile +121 -0
- data/Gemfile.lock +353 -0
- data/LICENSE +24 -0
- data/README.md +7 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/bin/punk +18 -0
- data/lib/punk.rb +32 -0
- data/lib/punk/commands/auth.rb +57 -0
- data/lib/punk/commands/generate.rb +54 -0
- data/lib/punk/commands/http.rb +71 -0
- data/lib/punk/commands/list.rb +28 -0
- data/lib/punk/config/console/defaults.json +5 -0
- data/lib/punk/config/defaults.json +47 -0
- data/lib/punk/config/schema.json +55 -0
- data/lib/punk/config/script/defaults.json +5 -0
- data/lib/punk/config/server/development.json +9 -0
- data/lib/punk/config/spec/defaults.json +5 -0
- data/lib/punk/core/app.rb +233 -0
- data/lib/punk/core/boot.rb +9 -0
- data/lib/punk/core/cli.rb +13 -0
- data/lib/punk/core/commander.rb +82 -0
- data/lib/punk/core/commands.rb +26 -0
- data/lib/punk/core/env.rb +290 -0
- data/lib/punk/core/error.rb +10 -0
- data/lib/punk/core/exec.rb +38 -0
- data/lib/punk/core/interface.rb +76 -0
- data/lib/punk/core/load.rb +9 -0
- data/lib/punk/core/logger.rb +33 -0
- data/lib/punk/core/monkey.rb +7 -0
- data/lib/punk/core/monkey_unreloader.rb +18 -0
- data/lib/punk/core/pry.rb +39 -0
- data/lib/punk/core/settings.rb +38 -0
- data/lib/punk/core/version.rb +7 -0
- data/lib/punk/core/worker.rb +13 -0
- data/lib/punk/framework/action.rb +29 -0
- data/lib/punk/framework/all.rb +10 -0
- data/lib/punk/framework/command.rb +117 -0
- data/lib/punk/framework/model.rb +52 -0
- data/lib/punk/framework/plugins/all.rb +3 -0
- data/lib/punk/framework/plugins/validation.rb +55 -0
- data/lib/punk/framework/runnable.rb +31 -0
- data/lib/punk/framework/service.rb +67 -0
- data/lib/punk/framework/view.rb +26 -0
- data/lib/punk/framework/worker.rb +34 -0
- data/lib/punk/helpers/all.rb +8 -0
- data/lib/punk/helpers/loggable.rb +79 -0
- data/lib/punk/helpers/publishable.rb +9 -0
- data/lib/punk/helpers/renderable.rb +75 -0
- data/lib/punk/helpers/swagger.rb +20 -0
- data/lib/punk/helpers/validatable.rb +57 -0
- data/lib/punk/plugins/all.rb +4 -0
- data/lib/punk/plugins/cors.rb +19 -0
- data/lib/punk/plugins/ssl.rb +13 -0
- data/lib/punk/startup/cache.rb +11 -0
- data/lib/punk/startup/database.rb +57 -0
- data/lib/punk/startup/environment.rb +10 -0
- data/lib/punk/startup/logger.rb +20 -0
- data/lib/punk/startup/task.rb +10 -0
- data/lib/punk/templates/fail.jbuilder +4 -0
- data/lib/punk/templates/fail.rcsv +6 -0
- data/lib/punk/templates/fail.slim +9 -0
- data/lib/punk/templates/fail.xml.slim +6 -0
- data/lib/punk/templates/info.jbuilder +3 -0
- data/lib/punk/templates/info.rcsv +2 -0
- data/lib/punk/templates/info.slim +6 -0
- data/lib/punk/templates/info.xml.slim +3 -0
- data/lib/punk/views/all.rb +4 -0
- data/lib/punk/views/fail.rb +21 -0
- data/lib/punk/views/info.rb +20 -0
- data/punk.gemspec +246 -0
- metadata +747 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aasm'
|
4
|
+
|
5
|
+
AASM::Configuration.hide_warnings = true
|
6
|
+
|
7
|
+
module PUNK
|
8
|
+
Model = Class.new(Sequel::Model)
|
9
|
+
Model.def_Model(self)
|
10
|
+
class Model
|
11
|
+
include AASM
|
12
|
+
include Loggable
|
13
|
+
|
14
|
+
plugin :validation_helpers
|
15
|
+
plugin :timestamps, update_on_create: true
|
16
|
+
plugin :boolean_readers
|
17
|
+
plugin :uuid
|
18
|
+
plugin :update_or_create
|
19
|
+
plugin :defaults_setter
|
20
|
+
plugin :touch
|
21
|
+
plugin :many_through_many
|
22
|
+
|
23
|
+
plugin PUNK::Plugins::Validation
|
24
|
+
|
25
|
+
def validate; end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
id.present? ? "#{id}|#{self}" : to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.sample_dataset(count=1)
|
32
|
+
order(Sequel.lit('random()')).limit(count)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.sample(count=1)
|
36
|
+
query = sample_dataset(count)
|
37
|
+
count == 1 ? query.first : query.all
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.symbolize(*names)
|
41
|
+
names.each do |name|
|
42
|
+
chain =
|
43
|
+
Module.new do
|
44
|
+
define_method(name) do |*args|
|
45
|
+
super(*args)&.to_sym
|
46
|
+
end
|
47
|
+
end
|
48
|
+
prepend chain
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'phony'
|
5
|
+
|
6
|
+
module PUNK
|
7
|
+
module Plugins
|
8
|
+
module Validation
|
9
|
+
module InstanceMethods
|
10
|
+
def validates_url(atts, opts={})
|
11
|
+
default = { message: "is not a URL" }
|
12
|
+
validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
|
13
|
+
message unless URI::DEFAULT_PARSER.make_regexp.match(value).to_a.compact.length > 2
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def validates_email(atts, opts={})
|
18
|
+
default = { message: "is not an email address" }
|
19
|
+
validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
|
20
|
+
message unless URI::MailTo::EMAIL_REGEXP.match(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validates_phone(atts, opts={})
|
25
|
+
default = { message: "is not a phone number" }
|
26
|
+
validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
|
27
|
+
message unless Phony.plausible?(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def validates_parse_id(atts, opts={})
|
32
|
+
default = { message: "is not a Parse ID" }
|
33
|
+
validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
|
34
|
+
message unless /^[[:alnum:]]{10}$/.match(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validates_subdomain(atts, opts={})
|
39
|
+
default = { message: "is not a subdomain" }
|
40
|
+
validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
|
41
|
+
message unless /^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/.match(value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def validates_state(name, state)
|
46
|
+
errors.add(name, "is not in #{state} state") unless self[name].send("#{state}?")
|
47
|
+
end
|
48
|
+
|
49
|
+
def validates_event(name, event)
|
50
|
+
errors.add(name, "may not #{event}") unless self[name].send("may_#{event}?")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PUNK
|
4
|
+
class Runnable < Settings
|
5
|
+
include Validatable
|
6
|
+
|
7
|
+
def self.args(*args)
|
8
|
+
PUNK.store.runnable ||= {}
|
9
|
+
return PUNK.store.runnable[name] if PUNK.store.runnable.key?(name)
|
10
|
+
PUNK.store.runnable[name] = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(key, *args, &block)
|
14
|
+
val = super
|
15
|
+
val = val.to_h if val.class == self.class || val.class.instance_of?(self.class)
|
16
|
+
val
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to_missing?(key, *args) # rubocop:disable Lint/UselessMethodDefinition
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _init_runnable(kwargs)
|
26
|
+
args = self.class.args || []
|
27
|
+
load(args.zip(Array.new(args.length, nil)).to_h)
|
28
|
+
load(kwargs.select { |k, _| args.include?(k) })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/string_inquirer'
|
4
|
+
|
5
|
+
module PUNK
|
6
|
+
class Service < Runnable
|
7
|
+
include Loggable
|
8
|
+
include Publishable
|
9
|
+
|
10
|
+
delegate :ready?, to: :_state
|
11
|
+
delegate :invalid?, to: :_state
|
12
|
+
delegate :success?, to: :_state
|
13
|
+
delegate :failure?, to: :_state
|
14
|
+
|
15
|
+
def self.run(**kwargs)
|
16
|
+
service = new
|
17
|
+
service.send(:_init, **kwargs)
|
18
|
+
service.send(:_run)
|
19
|
+
hijack = service.send(:_callbacks)
|
20
|
+
service.is_a?(View) && hijack.is_a?(View) ? hijack : service
|
21
|
+
end
|
22
|
+
|
23
|
+
def result
|
24
|
+
@_result
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
raise NotImplemented, "view must provide process method"
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def _state
|
34
|
+
ActiveSupport::StringInquirer.new(@_state.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_success; end
|
38
|
+
|
39
|
+
def on_failure; end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def _init(**kwargs)
|
44
|
+
@_state = :ready
|
45
|
+
@_result = nil
|
46
|
+
_init_runnable(kwargs)
|
47
|
+
end
|
48
|
+
|
49
|
+
def _run
|
50
|
+
unless ready?
|
51
|
+
errors.add(:raised, e.message)
|
52
|
+
@_state = :failure
|
53
|
+
return
|
54
|
+
end
|
55
|
+
unless valid?
|
56
|
+
@_state = :invalid
|
57
|
+
return
|
58
|
+
end
|
59
|
+
@_result = process
|
60
|
+
@_state = :success
|
61
|
+
end
|
62
|
+
|
63
|
+
def _callbacks
|
64
|
+
success? ? on_success : on_failure
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PUNK
|
4
|
+
class View < Service
|
5
|
+
include Renderable
|
6
|
+
|
7
|
+
def self.present(**kwargs)
|
8
|
+
profile_info("present", kwargs) { run(**kwargs) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
raise NotImplemented, "view must provide process method"
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def on_success
|
18
|
+
raise InternalServerError, "not a template: #{result}" unless result.is_a?(String)
|
19
|
+
template(result)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_failure
|
23
|
+
Fail.run(message: "view failed: #{self.class}", error_messages: errors.full_messages, status: invalid? ? 400 : 500) unless is_a?(Fail)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sidekiq'
|
4
|
+
|
5
|
+
module PUNK
|
6
|
+
class Worker < Runnable
|
7
|
+
include Sidekiq::Worker
|
8
|
+
|
9
|
+
def self.perform_now(**kwargs)
|
10
|
+
worker = new
|
11
|
+
worker.define_singleton_method :logger, -> { SemanticLogger['PUNK::SKQ'] }
|
12
|
+
worker.send(:perform, **kwargs)
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform(kwargs={})
|
16
|
+
@_started = Time.now.utc
|
17
|
+
logger.info "Started #{self.class.name}", kwargs.sanitize.inspect
|
18
|
+
logger.push_tags(self.class.name)
|
19
|
+
_init_runnable(kwargs.deep_symbolize_keys)
|
20
|
+
raise BadRequest, "validation failed" unless valid?
|
21
|
+
process
|
22
|
+
nil
|
23
|
+
rescue BadRequest => e
|
24
|
+
logger.error e.message
|
25
|
+
logger.error errors.full_messages.to_sentence
|
26
|
+
raise
|
27
|
+
ensure
|
28
|
+
duration = 1000.0 * (Time.now.utc - @_started)
|
29
|
+
logger.pop_tags
|
30
|
+
logger.info message: "Completed #{self.class.name}", duration: duration
|
31
|
+
SemanticLogger.flush
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PUNK
|
4
|
+
module Loggable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def logger
|
8
|
+
self.class.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def profile_info(name, **kwargs, &block)
|
12
|
+
self.class.profile_info(name, **kwargs, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def profile_debug(name, **kwargs, &block)
|
16
|
+
self.class.profile_debug(name, **kwargs, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def profile_trace(name, **kwargs, &block)
|
20
|
+
self.class.profile_trace(name, **kwargs, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exception(e, extra={})
|
24
|
+
if ENV.key?('SENTRY_DSN')
|
25
|
+
::Raven.capture_exception(
|
26
|
+
e,
|
27
|
+
message: e.message,
|
28
|
+
extra: extra,
|
29
|
+
transaction: "Punk!"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
_clean(e)
|
33
|
+
logger.error exception: e
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
IGNORE = Gem.path + $LOAD_PATH
|
39
|
+
|
40
|
+
def _clean(e, trim: false)
|
41
|
+
_clean(e.cause, trim: true) if e.cause
|
42
|
+
ignore = IGNORE.reject { |path| e.backtrace.first =~ /#{path}/ }
|
43
|
+
skip = false
|
44
|
+
e.backtrace.map! do |line|
|
45
|
+
if trim || ignore.any? { |path| line =~ /#{path}/ }
|
46
|
+
unless skip
|
47
|
+
skip = true
|
48
|
+
'...'
|
49
|
+
end
|
50
|
+
else
|
51
|
+
skip = false
|
52
|
+
line
|
53
|
+
end
|
54
|
+
end
|
55
|
+
e.backtrace.compact!
|
56
|
+
end
|
57
|
+
|
58
|
+
class_methods do
|
59
|
+
def logger
|
60
|
+
SemanticLogger[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
def profile_info(name, **kwargs)
|
64
|
+
logger.info "Started #{name}", kwargs.sanitize.inspect
|
65
|
+
logger.measure_info("Completed #{name}") { logger.tagged(name) { yield } }
|
66
|
+
end
|
67
|
+
|
68
|
+
def profile_debug(name, **kwargs)
|
69
|
+
logger.debug "Started #{name}", kwargs.sanitize.inspect
|
70
|
+
logger.measure_debug("Completed #{name}") { logger.tagged(name) { yield } }
|
71
|
+
end
|
72
|
+
|
73
|
+
def profile_trace(name, **kwargs)
|
74
|
+
logger.trace "Started #{name}", kwargs.sanitize.inspect
|
75
|
+
logger.measure_trace("Completed #{name}") { logger.tagged(name) { yield } }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tilt'
|
4
|
+
|
5
|
+
module PUNK
|
6
|
+
module Renderable
|
7
|
+
FORMATS =
|
8
|
+
{
|
9
|
+
html: { renderer: :to_html, extension: 'slim' },
|
10
|
+
json: { renderer: :to_json, extension: 'jbuilder' },
|
11
|
+
csv: { renderer: :to_csv, extension: 'rcsv' },
|
12
|
+
xml: { renderer: :to_xml, extension: 'xml.slim' }
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def template(name)
|
16
|
+
@template = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(format)
|
20
|
+
raise NotFound, "unknown format '#{format}'" unless FORMATS.key?(format)
|
21
|
+
send(FORMATS[format][:renderer])
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_html(options={})
|
25
|
+
_render(:html, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_json(options={})
|
29
|
+
_render(:json, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_csv(options={})
|
33
|
+
_render(:csv, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_xml(options={})
|
37
|
+
_render(:xml, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_h
|
49
|
+
ActiveSupport::JSON.decode(to_json).to_h.deep_symbolize_keys
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def _dir
|
55
|
+
File.join(PUNK.get.app.path, 'templates')
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def _path(format)
|
61
|
+
raise InternalServerError, "No template given" unless @template
|
62
|
+
base = File.join(_dir, @template)
|
63
|
+
ext = FORMATS[format][:extension]
|
64
|
+
"#{base}.#{ext}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def _render(format, options)
|
68
|
+
path = _path(format)
|
69
|
+
raise NotImplemented, "No path for template: #{@template}" unless path
|
70
|
+
Tilt.new(path).render(self, options)
|
71
|
+
rescue LoadError, Errno::ENOENT
|
72
|
+
raise NotFound, "Cannot load template: #{path}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|