shaf 0.8.0 → 1.0.0

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 (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/bin/shaf +19 -4
  5. data/lib/shaf.rb +1 -0
  6. data/lib/shaf/app.rb +7 -10
  7. data/lib/shaf/command/server.rb +4 -2
  8. data/lib/shaf/errors.rb +22 -2
  9. data/lib/shaf/extensions/controller_hooks.rb +14 -0
  10. data/lib/shaf/extensions/resource_uris.rb +1 -1
  11. data/lib/shaf/formable/field.rb +10 -6
  12. data/lib/shaf/formable/form.rb +4 -0
  13. data/lib/shaf/generator/base.rb +2 -2
  14. data/lib/shaf/generator/controller.rb +11 -11
  15. data/lib/shaf/generator/helper.rb +33 -0
  16. data/lib/shaf/generator/migration.rb +6 -1
  17. data/lib/shaf/generator/migration/empty.rb +2 -2
  18. data/lib/shaf/generator/serializer.rb +4 -4
  19. data/lib/shaf/generator/templates/api/controller.rb.erb +4 -3
  20. data/lib/shaf/generator/templates/api/serializer.rb.erb +10 -12
  21. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +4 -2
  22. data/lib/shaf/helpers.rb +2 -0
  23. data/lib/shaf/helpers/http_header.rb +26 -0
  24. data/lib/shaf/helpers/paginate.rb +4 -2
  25. data/lib/shaf/helpers/payload.rb +33 -8
  26. data/lib/shaf/middleware/request_id.rb +3 -1
  27. data/lib/shaf/rake/db.rb +5 -5
  28. data/lib/shaf/rake/test.rb +0 -27
  29. data/lib/shaf/router.rb +133 -0
  30. data/lib/shaf/settings.rb +36 -11
  31. data/lib/shaf/spec/http_method_utils.rb +11 -3
  32. data/lib/shaf/spec/integration_spec.rb +4 -4
  33. data/lib/shaf/spec/serializer_spec.rb +1 -1
  34. data/lib/shaf/upgrade/manifest.rb +34 -9
  35. data/lib/shaf/upgrade/package.rb +24 -15
  36. data/lib/shaf/utils.rb +31 -21
  37. data/lib/shaf/version.rb +1 -1
  38. data/templates/Rakefile +27 -0
  39. data/templates/api/controllers/base_controller.rb +13 -8
  40. data/templates/api/serializers/error_serializer.rb +3 -1
  41. data/templates/api/serializers/form_serializer.rb +11 -16
  42. data/templates/api/serializers/validation_error_serializer.rb +13 -0
  43. data/templates/config.ru +1 -1
  44. data/templates/config/bootstrap.rb +3 -1
  45. data/templates/config/database.rb +6 -7
  46. data/templates/config/directories.rb +3 -2
  47. data/templates/config/helpers.rb +1 -1
  48. data/templates/config/initializers/db_migrations.rb +14 -8
  49. data/templates/config/initializers/logging.rb +2 -2
  50. data/templates/config/initializers/sequel.rb +1 -0
  51. data/templates/config/paths.rb +7 -0
  52. data/templates/config/settings.yml +5 -0
  53. data/upgrades/1.0.0.tar.gz +0 -0
  54. metadata +38 -19
  55. metadata.gz.sig +0 -0
  56. data/templates/config/constants.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9eb918ad2c43059ac408c1bd9d85e41e3c3914510a22ed08628ffc9984f1777
4
- data.tar.gz: b7f1593151ad34cac7e0edde0841cde9dc3494f72d1292835814870b733681f8
3
+ metadata.gz: d5e022f8371c152548e409bfdf7711fa7dcd91e54b93cecfc341c09d9beee963
4
+ data.tar.gz: ac93080f87f7663402225692f402789fdae5b8605ec5bbba44b8a6fd8fb9bd4b
5
5
  SHA512:
6
- metadata.gz: 6b76e3caf4a9e844b7c40e97e56ef37b332db9b3466efb585ad786dbbd7461f22b3b9088c758df2313eef3fcd2832ef880419f4b322ce63063861f4db8ed72fb
7
- data.tar.gz: 6818d566651548ff3e8244e87fc98263b4ba52cffb728618a4c0c8822e20a9f043633ee9b4d6e1c8bbd443354f3de1c753f1ecc7255631cf5036c46560565d9e
6
+ metadata.gz: 5607ab31104b40ab08cbec25ae47a382e616ddaf8239b411b82509afd7bc995cd867d070fd86a8bb3f8596534f83d2e97338c8d72c9727e410196dfd8c237fa5
7
+ data.tar.gz: c5bd80f13128e66384a0a01a5a24137a6507509bca7a2ae5a01b2b2c36c2eb2515c2b3e082ed0ba2e55238aaa982441300c7121fa5b7d63176033ab839b92846
Binary file
data.tar.gz.sig CHANGED
Binary file
data/bin/shaf CHANGED
@@ -16,17 +16,23 @@ module Shaf
16
16
  check_customizations
17
17
  return show_help if show_help?
18
18
  Command::Factory.create(*ARGV).call
19
- rescue RegistrableFactory::NotFoundError, Command::ArgumentError => e
20
- STDERR.puts e.message, "\n"
19
+ rescue RegistrableFactory::NotFoundError, Command::ArgumentError => err
20
+ STDERR.puts err.message, "\n"
21
+ show_backtrace(err)
21
22
  show_help
22
23
  exit 1
23
24
  rescue Utils::ProjectRootNotFound
24
25
  STDERR.puts "This command can only be executed inside a Shaf project directory. " \
25
26
  "Please change directory and try again!"
26
27
  exit 2
27
- rescue Command::CommandError => e
28
- STDERR.puts "Command failed: #{e.message}\n"
28
+ rescue Command::CommandError => err
29
+ STDERR.puts "Command failed: #{err.message}\n"
30
+ show_backtrace(err)
29
31
  exit 3
32
+ rescue StandardError => err
33
+ STDERR.puts err.message, "\n"
34
+ show_backtrace(err, force: true)
35
+ exit 4
30
36
  end
31
37
 
32
38
  def show_help
@@ -45,6 +51,15 @@ module Shaf
45
51
  ARGV.first =~ /-h/
46
52
  end
47
53
 
54
+ def show_backtrace(err, force: false)
55
+ if force || ARGV.include?('--trace')
56
+ err = err.cause while err.cause
57
+ STDERR.puts err.backtrace
58
+ else
59
+ STDERR.puts 'run the command with "--trace" to see a stack trace'
60
+ end
61
+ end
62
+
48
63
  def check_customizations
49
64
  return unless project_root
50
65
 
@@ -2,6 +2,7 @@ require 'shaf/version'
2
2
  require 'shaf/settings'
3
3
  require 'shaf/command'
4
4
  require 'shaf/app'
5
+ require 'shaf/router'
5
6
  require 'shaf/errors'
6
7
  require 'shaf/formable'
7
8
  require 'shaf/extensions'
@@ -3,20 +3,17 @@ require 'shaf/middleware'
3
3
  module Shaf
4
4
  class App
5
5
  class << self
6
- def instance
7
- @instance ||= create_instance
6
+ def run!
7
+ app.run!
8
8
  end
9
9
 
10
- def create_instance
11
- Sinatra.new.tap do |instance|
12
- instance.set :port, Settings.port || 3000
13
- instance.use Shaf::Middleware::RequestId
10
+ def app
11
+ Sinatra.new.tap do |app|
12
+ app.set :port, Settings.port || 3000
13
+ app.use Middleware::RequestId
14
+ app.use Router
14
15
  end
15
16
  end
16
-
17
- def use(middleware)
18
- instance.use middleware
19
- end
20
17
  end
21
18
  end
22
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shaf
2
4
  module Command
3
5
  class Server < Base
@@ -6,7 +8,7 @@ module Shaf
6
8
  usage 'server'
7
9
 
8
10
  def self.options(parser, options)
9
- parser.on("-p", "--port PORT", Integer, "Listen port") do |p|
11
+ parser.on('-p', '--port PORT', Integer, 'Listen port') do |p|
10
12
  options[:port] = p
11
13
  end
12
14
  end
@@ -14,7 +16,7 @@ module Shaf
14
16
  def call
15
17
  Settings.port = options[:port] if options[:port]
16
18
  bootstrap
17
- App.instance.run!
19
+ App.run!
18
20
  end
19
21
  end
20
22
  end
@@ -64,7 +64,7 @@ module Shaf
64
64
  end
65
65
  end
66
66
 
67
- class ConflictError
67
+ class ConflictError < ServerError
68
68
  def http_status
69
69
  409
70
70
  end
@@ -87,7 +87,7 @@ module Shaf
87
87
  end
88
88
  end
89
89
 
90
- class UnprocessableEntityError
90
+ class UnprocessableEntityError < ServerError
91
91
  def http_status
92
92
  422
93
93
  end
@@ -97,5 +97,25 @@ module Shaf
97
97
  super(msg, code: "UNPROCESSABLE_ENTITY", title: "Request can not be processed")
98
98
  end
99
99
  end
100
+
101
+ class ValidationError < ServerError
102
+ attr_reader :fields
103
+
104
+ def self.from_sequel(validation_failed)
105
+ new(validation_failed.message, validation_failed.errors).tap do |err|
106
+ err.set_backtrace(validation_failed.backtrace)
107
+ end
108
+ end
109
+
110
+ def http_status
111
+ 422
112
+ end
113
+
114
+ def initialize(msg, fields)
115
+ msg ||= "The entity being created/updated is invalid"
116
+ super(msg, code: "VALIDATION_ERROR", title: "Invalid entity")
117
+ @fields = fields || {}
118
+ end
119
+ end
100
120
  end
101
121
  end
@@ -15,6 +15,7 @@ module Shaf
15
15
  def __action_hook(hook, method_name, block, **options)
16
16
  only = Array(options[:only]) if options.key? :only
17
17
  except = Array(options[:except]) if options.key? :except
18
+ validate_symbols(hook: hook, only: only, except: except)
18
19
 
19
20
  path_helpers.each do |helper|
20
21
  next if only && !only.include?(helper)
@@ -41,6 +42,19 @@ module Shaf
41
42
  str = template.gsub(%r{:[^/]*}, '\w+')
42
43
  Regexp.new("#{str}/?")
43
44
  end
45
+
46
+ def validate_symbols(hook:, only: [], except: [])
47
+ s = "\n\n\"%s\" given as :%s keyword arg to %s hook. Must be a symbol!\n"
48
+ Array(only).each do |value|
49
+ next if value.is_a? Symbol
50
+ log.warn format(s, value, 'only', hook)
51
+ end
52
+
53
+ Array(except).each do |value|
54
+ next if value.is_a? Symbol
55
+ log.warn format(s, value, 'except', hook)
56
+ end
57
+ end
44
58
  end
45
59
 
46
60
  Sinatra.register ControllerHooks
@@ -75,7 +75,7 @@ module Shaf
75
75
 
76
76
  def self.base_uri
77
77
  protocol = Settings.protocol || 'http'
78
- host = Settings.host || 'localhost'
78
+ host = Settings.hostname || 'localhost'
79
79
  port = Settings.port ? ":#{Settings.port}" : ""
80
80
  "#{protocol}://#{host}#{port}"
81
81
  end
@@ -5,7 +5,9 @@ module Shaf
5
5
  class Field
6
6
  extend Shaf::ImmutableAttr
7
7
 
8
- immutable_reader :name, :type, :value, :label, :required, :accessor_name
8
+ immutable_reader :name, :type, :value, :title, :required, :hidden, :accessor_name
9
+
10
+ alias label title
9
11
 
10
12
  HTML_TYPE_MAPPINGS = {
11
13
  string: 'text',
@@ -15,11 +17,12 @@ module Shaf
15
17
  def initialize(name, params = {})
16
18
  @name = name
17
19
  @type = params[:type]&.to_sym
18
- @label = params[:label]
20
+ @title = params[:title] || params[:label]
21
+ @hidden = params.fetch(:hidden, false)
19
22
  @has_value = params.key? :value
20
23
  @value = params[:value]
21
- @required = params[:required] || false
22
- @accessor_name = (params[:accessor_name] || name).to_sym
24
+ @required = params.fetch(:required, false)
25
+ @accessor_name = params.fetch(:accessor_name, name).to_sym
23
26
  end
24
27
 
25
28
  def has_value?
@@ -43,15 +46,16 @@ module Shaf
43
46
  private
44
47
 
45
48
  def label_element
46
- str = (label || name || "").to_s
49
+ str = (title || name || "").to_s
47
50
  %Q(<label for="#{name}" class="form--label">#{str}</label>)
48
51
  end
49
52
 
50
53
  def input_element
51
54
  _value = value ? %Q(value="#{value.to_s}") : nil
52
55
  _required = required ? "required" : nil
56
+ _type = hidden ? "hidden" : HTML_TYPE_MAPPINGS.fetch(type.to_s, 'text')
53
57
  attributes = [
54
- %Q(type="#{HTML_TYPE_MAPPINGS[type.to_s]}"),
58
+ %Q(type="#{_type}"),
55
59
  'class="form--input"',
56
60
  %Q(id="#{name.to_s}"),
57
61
  %Q(name="#{name.to_s}"),
@@ -73,6 +73,10 @@ module Shaf
73
73
  end
74
74
  end
75
75
 
76
+ def [](name)
77
+ fields.find { |field| field.name == name }
78
+ end
79
+
76
80
  def to_html
77
81
  form_element do
78
82
  [
@@ -1,6 +1,6 @@
1
1
  require 'fileutils'
2
2
  require 'erb'
3
- require 'ostruct'
3
+ require 'shaf/generator/helper'
4
4
 
5
5
  module Shaf
6
6
  module Generator
@@ -44,7 +44,7 @@ module Shaf
44
44
  def render(template, locals = {})
45
45
  str = read_template(template)
46
46
  locals[:changes] ||= []
47
- b = OpenStruct.new(locals).instance_eval { binding }
47
+ b = Helper.new(locals).binding
48
48
 
49
49
  return ERB.new(str, 0, '%-<>').result(b) if RUBY_VERSION < "2.6.0"
50
50
  ERB.new(str,trim_mode: '-<>').result(b)
@@ -16,22 +16,22 @@ module Shaf
16
16
  end
17
17
 
18
18
  def name
19
- n = args.first || ""
19
+ n = args.first || ''
20
20
  return n unless n.empty?
21
21
  raise Command::ArgumentError,
22
- "Please provide a controller name when using the controller generator!"
22
+ 'Please provide a controller name when using the controller generator!'
23
23
  end
24
24
 
25
25
  def model_class_name
26
- Utils::model_name(name)
26
+ Utils.model_name(name)
27
27
  end
28
28
 
29
29
  def plural_name
30
- Utils::pluralize(name)
30
+ Utils.pluralize(name)
31
31
  end
32
32
 
33
33
  def pluralized_model_name
34
- Utils::pluralize(model_class_name)
34
+ Utils.pluralize(model_class_name)
35
35
  end
36
36
 
37
37
  def template
@@ -74,16 +74,16 @@ module Shaf
74
74
  end
75
75
 
76
76
  def add_link_to_root
77
- file = "api/serializers/root_serializer.rb"
77
+ file = 'api/serializers/root_serializer.rb'
78
78
  unless File.exist? file
79
79
  puts "Warning: file '#{file}' does not exist. "\
80
80
  "Skip adding link to the #{plural_name} collection"
81
81
  end
82
82
  added = false
83
83
  content = []
84
- File.readlines(file).reverse.each do |line|
84
+ File.readlines(file).reverse_each do |line|
85
85
  if match = !added && line.match(/^(\s*)link /)
86
- content.unshift link_content("#{match[1]}")
86
+ content.unshift link_content(match[1].to_s)
87
87
  added = true
88
88
  end
89
89
  content.unshift(line)
@@ -92,8 +92,8 @@ module Shaf
92
92
  puts "Modified: #{file}"
93
93
  end
94
94
 
95
- def link_content(indentation = "")
96
- <<~EOS.split("\n").map { |line| "#{indentation}#{line}" }
95
+ def link_content(indentation = '')
96
+ <<~DOC.split("\n").map { |line| "#{indentation}#{line}" }
97
97
 
98
98
  # Auto generated doc:
99
99
  # Link to the collection of #{plural_name}.
@@ -105,7 +105,7 @@ module Shaf
105
105
  # /#{plural_name}
106
106
  #```
107
107
  link :#{plural_name}, #{plural_name}_uri
108
- EOS
108
+ DOC
109
109
  end
110
110
  end
111
111
  end
@@ -0,0 +1,33 @@
1
+ require 'ostruct'
2
+
3
+ module Shaf
4
+ module Generator
5
+ class Helper < OpenStruct
6
+ # public method mapped to Kernel's private #binding
7
+ def binding
8
+ super
9
+ end
10
+
11
+ def print(lines, indent_level = 2)
12
+ strip_blank(lines).inject do |result, line|
13
+ result + "\n#{i(indent_level) unless line.empty?}#{line}"
14
+ end.chomp
15
+ end
16
+
17
+ def print_nested(sections, indent_level = 2)
18
+ sections.map(&method(:print)).join("\n\n#{i(indent_level)}")
19
+ end
20
+
21
+ def strip_blank(lines)
22
+ lines.map do |line|
23
+ line.strip.empty? ? '' : line
24
+ end
25
+ end
26
+
27
+ def indentation(level)
28
+ ' ' * level
29
+ end
30
+ alias i indentation
31
+ end
32
+ end
33
+ end
@@ -11,7 +11,12 @@ module Shaf
11
11
  usage { Factory.usage }
12
12
 
13
13
  def call
14
- generator = args.empty? ? Empty.new(**options) : Factory.create(*args, **options)
14
+ generator =
15
+ if Factory.lookup(*args)
16
+ Factory.create(*args, **options)
17
+ else
18
+ Empty.new(*args, **options)
19
+ end
15
20
  (target, content) = generator.call
16
21
  write_output(target, content)
17
22
  rescue StandardError => e
@@ -4,12 +4,12 @@ module Shaf
4
4
  class Empty < Base
5
5
 
6
6
  identifier %r(\A\Z)
7
- usage 'generate migration'
7
+ usage 'generate migration [name]'
8
8
 
9
9
  def validate_args; end
10
10
 
11
11
  def compile_migration_name
12
- "empty"
12
+ args.first || "empty"
13
13
  end
14
14
 
15
15
  def compile_changes
@@ -22,7 +22,7 @@ module Shaf
22
22
  end
23
23
 
24
24
  def model_class_name
25
- Utils::model_name(name)
25
+ Utils.model_name(name)
26
26
  end
27
27
 
28
28
  def policy_class_name
@@ -155,7 +155,7 @@ module Shaf
155
155
  # #{desc}.
156
156
  # Method: #{method}
157
157
  #{example(method, uri)}
158
- link :'#{rel}' do
158
+ link #{Utils.symbol_string(rel)} do
159
159
  #{uri_helper}
160
160
  end
161
161
  EOS
@@ -188,7 +188,7 @@ module Shaf
188
188
  curie(:doc) { doc_curie_uri('#{name}') }
189
189
 
190
190
  link :self, #{plural_name}_uri
191
- link :up, root_uri
191
+ link :'doc:up', root_uri
192
192
 
193
193
  #{create_link.join("\n ")}
194
194
  end
@@ -207,7 +207,7 @@ module Shaf
207
207
  attributes_with_doc: attributes_with_doc,
208
208
  curies_with_doc: curies_with_doc,
209
209
  links_with_doc: links_with_doc,
210
- collection_with_doc: collection_with_doc,
210
+ collection_with_doc: collection_with_doc
211
211
  }
212
212
  end
213
213
 
@@ -21,7 +21,7 @@ class <%= controller_class_name %> < BaseController
21
21
  post :<%= plural_name %>_path do
22
22
  authorize! :write
23
23
  <%= name %> = <%= model_class_name %>.create(<%= name %>_params)
24
- headers({ "Location" => <%= name %>_uri(<%= name %>) })
24
+ headers('Location' => <%= name %>_uri(<%= name %>))
25
25
  respond_with <%= name %>, status: 201
26
26
  end
27
27
 
@@ -31,7 +31,7 @@ class <%= controller_class_name %> < BaseController
31
31
  end
32
32
 
33
33
  get :edit_<%= name %>_path do
34
- authorize! :read
34
+ authorize! :write
35
35
  cache_control(:private, http_cache_max_age: :short)
36
36
  respond_with edit_form
37
37
  end
@@ -49,7 +49,8 @@ class <%= controller_class_name %> < BaseController
49
49
  end
50
50
 
51
51
  def <%= name %>_params
52
- # Generated method - TODO: Remove any params that should not be allowed!
52
+ # Generated method
53
+ # TODO: Remove any params that should not be allowed for mass update/create!
53
54
  safe_params(<%= params.map { |p| ":#{p[0]}" }.join(', ') %>)
54
55
  end
55
56