punk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/Gemfile +121 -0
  4. data/Gemfile.lock +353 -0
  5. data/LICENSE +24 -0
  6. data/README.md +7 -0
  7. data/Rakefile +31 -0
  8. data/VERSION +1 -0
  9. data/bin/punk +18 -0
  10. data/lib/punk.rb +32 -0
  11. data/lib/punk/commands/auth.rb +57 -0
  12. data/lib/punk/commands/generate.rb +54 -0
  13. data/lib/punk/commands/http.rb +71 -0
  14. data/lib/punk/commands/list.rb +28 -0
  15. data/lib/punk/config/console/defaults.json +5 -0
  16. data/lib/punk/config/defaults.json +47 -0
  17. data/lib/punk/config/schema.json +55 -0
  18. data/lib/punk/config/script/defaults.json +5 -0
  19. data/lib/punk/config/server/development.json +9 -0
  20. data/lib/punk/config/spec/defaults.json +5 -0
  21. data/lib/punk/core/app.rb +233 -0
  22. data/lib/punk/core/boot.rb +9 -0
  23. data/lib/punk/core/cli.rb +13 -0
  24. data/lib/punk/core/commander.rb +82 -0
  25. data/lib/punk/core/commands.rb +26 -0
  26. data/lib/punk/core/env.rb +290 -0
  27. data/lib/punk/core/error.rb +10 -0
  28. data/lib/punk/core/exec.rb +38 -0
  29. data/lib/punk/core/interface.rb +76 -0
  30. data/lib/punk/core/load.rb +9 -0
  31. data/lib/punk/core/logger.rb +33 -0
  32. data/lib/punk/core/monkey.rb +7 -0
  33. data/lib/punk/core/monkey_unreloader.rb +18 -0
  34. data/lib/punk/core/pry.rb +39 -0
  35. data/lib/punk/core/settings.rb +38 -0
  36. data/lib/punk/core/version.rb +7 -0
  37. data/lib/punk/core/worker.rb +13 -0
  38. data/lib/punk/framework/action.rb +29 -0
  39. data/lib/punk/framework/all.rb +10 -0
  40. data/lib/punk/framework/command.rb +117 -0
  41. data/lib/punk/framework/model.rb +52 -0
  42. data/lib/punk/framework/plugins/all.rb +3 -0
  43. data/lib/punk/framework/plugins/validation.rb +55 -0
  44. data/lib/punk/framework/runnable.rb +31 -0
  45. data/lib/punk/framework/service.rb +67 -0
  46. data/lib/punk/framework/view.rb +26 -0
  47. data/lib/punk/framework/worker.rb +34 -0
  48. data/lib/punk/helpers/all.rb +8 -0
  49. data/lib/punk/helpers/loggable.rb +79 -0
  50. data/lib/punk/helpers/publishable.rb +9 -0
  51. data/lib/punk/helpers/renderable.rb +75 -0
  52. data/lib/punk/helpers/swagger.rb +20 -0
  53. data/lib/punk/helpers/validatable.rb +57 -0
  54. data/lib/punk/plugins/all.rb +4 -0
  55. data/lib/punk/plugins/cors.rb +19 -0
  56. data/lib/punk/plugins/ssl.rb +13 -0
  57. data/lib/punk/startup/cache.rb +11 -0
  58. data/lib/punk/startup/database.rb +57 -0
  59. data/lib/punk/startup/environment.rb +10 -0
  60. data/lib/punk/startup/logger.rb +20 -0
  61. data/lib/punk/startup/task.rb +10 -0
  62. data/lib/punk/templates/fail.jbuilder +4 -0
  63. data/lib/punk/templates/fail.rcsv +6 -0
  64. data/lib/punk/templates/fail.slim +9 -0
  65. data/lib/punk/templates/fail.xml.slim +6 -0
  66. data/lib/punk/templates/info.jbuilder +3 -0
  67. data/lib/punk/templates/info.rcsv +2 -0
  68. data/lib/punk/templates/info.slim +6 -0
  69. data/lib/punk/templates/info.xml.slim +3 -0
  70. data/lib/punk/views/all.rb +4 -0
  71. data/lib/punk/views/fail.rb +21 -0
  72. data/lib/punk/views/info.rb +20 -0
  73. data/punk.gemspec +246 -0
  74. 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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validation'
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ require_relative 'loggable'
6
+ require_relative 'publishable'
7
+ require_relative 'renderable'
8
+ require_relative 'validatable'
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wisper'
4
+
5
+ module PUNK
6
+ module Publishable
7
+ include Wisper::Publisher
8
+ end
9
+ 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