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