shaf 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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