shaf 0.1.0.beta

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 (90) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -0
  4. data/bin/shaf +57 -0
  5. data/lib/shaf.rb +9 -0
  6. data/lib/shaf/api_doc.rb +124 -0
  7. data/lib/shaf/api_doc/comment.rb +27 -0
  8. data/lib/shaf/api_doc/document.rb +133 -0
  9. data/lib/shaf/app.rb +22 -0
  10. data/lib/shaf/command.rb +42 -0
  11. data/lib/shaf/command/console.rb +17 -0
  12. data/lib/shaf/command/generate.rb +19 -0
  13. data/lib/shaf/command/new.rb +79 -0
  14. data/lib/shaf/command/server.rb +15 -0
  15. data/lib/shaf/command/templates/Gemfile.erb +30 -0
  16. data/lib/shaf/doc_model.rb +54 -0
  17. data/lib/shaf/errors.rb +77 -0
  18. data/lib/shaf/extensions.rb +11 -0
  19. data/lib/shaf/extensions/authorize.rb +42 -0
  20. data/lib/shaf/extensions/resource_uris.rb +153 -0
  21. data/lib/shaf/formable.rb +188 -0
  22. data/lib/shaf/generator.rb +69 -0
  23. data/lib/shaf/generator/controller.rb +106 -0
  24. data/lib/shaf/generator/migration.rb +122 -0
  25. data/lib/shaf/generator/migration/add_column.rb +49 -0
  26. data/lib/shaf/generator/migration/create_table.rb +40 -0
  27. data/lib/shaf/generator/migration/drop_column.rb +45 -0
  28. data/lib/shaf/generator/migration/empty.rb +21 -0
  29. data/lib/shaf/generator/migration/rename_column.rb +48 -0
  30. data/lib/shaf/generator/model.rb +68 -0
  31. data/lib/shaf/generator/policy.rb +43 -0
  32. data/lib/shaf/generator/scaffold.rb +26 -0
  33. data/lib/shaf/generator/serializer.rb +258 -0
  34. data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
  35. data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
  36. data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
  37. data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
  38. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
  39. data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
  40. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
  41. data/lib/shaf/helpers.rb +15 -0
  42. data/lib/shaf/helpers/json_html.rb +65 -0
  43. data/lib/shaf/helpers/paginate.rb +24 -0
  44. data/lib/shaf/helpers/payload.rb +115 -0
  45. data/lib/shaf/helpers/session.rb +53 -0
  46. data/lib/shaf/middleware.rb +1 -0
  47. data/lib/shaf/middleware/request_id.rb +16 -0
  48. data/lib/shaf/registrable_factory.rb +71 -0
  49. data/lib/shaf/settings.rb +33 -0
  50. data/lib/shaf/spec.rb +6 -0
  51. data/lib/shaf/spec/http_method_utils.rb +24 -0
  52. data/lib/shaf/spec/integration_spec.rb +53 -0
  53. data/lib/shaf/spec/model.rb +17 -0
  54. data/lib/shaf/spec/payload_test.rb +78 -0
  55. data/lib/shaf/spec/payload_utils.rb +176 -0
  56. data/lib/shaf/spec/serializer_spec.rb +24 -0
  57. data/lib/shaf/tasks.rb +4 -0
  58. data/lib/shaf/tasks/db.rb +61 -0
  59. data/lib/shaf/tasks/test.rb +43 -0
  60. data/lib/shaf/utils.rb +53 -0
  61. data/lib/shaf/version.rb +3 -0
  62. data/templates/Rakefile +13 -0
  63. data/templates/api/controllers/base_controller.rb +57 -0
  64. data/templates/api/controllers/docs_controller.rb +16 -0
  65. data/templates/api/controllers/root_controller.rb +8 -0
  66. data/templates/api/serializers/error_serializer.rb +10 -0
  67. data/templates/api/serializers/form_serializer.rb +42 -0
  68. data/templates/api/serializers/root_serializer.rb +16 -0
  69. data/templates/config.ru +4 -0
  70. data/templates/config/bootstrap.rb +12 -0
  71. data/templates/config/constants.rb +5 -0
  72. data/templates/config/customize.rb +3 -0
  73. data/templates/config/database.rb +40 -0
  74. data/templates/config/directories.rb +32 -0
  75. data/templates/config/helpers.rb +18 -0
  76. data/templates/config/initializers.rb +12 -0
  77. data/templates/config/initializers/db_migrations.rb +18 -0
  78. data/templates/config/initializers/hal_presenter.rb +6 -0
  79. data/templates/config/initializers/logging.rb +7 -0
  80. data/templates/config/initializers/sequel.rb +4 -0
  81. data/templates/config/settings.yml +19 -0
  82. data/templates/frontend/assets/css/main.css +70 -0
  83. data/templates/frontend/views/form.erb +16 -0
  84. data/templates/frontend/views/layout.erb +11 -0
  85. data/templates/frontend/views/payload.erb +8 -0
  86. data/templates/spec/integration/root_spec.rb +14 -0
  87. data/templates/spec/serializers/root_serializer_spec.rb +12 -0
  88. data/templates/spec/spec_helper.rb +4 -0
  89. metadata +348 -0
  90. metadata.gz.sig +0 -0
@@ -0,0 +1,188 @@
1
+ module Shaf
2
+ module Formable
3
+ class Field
4
+ attr_reader :name, :type, :value, :label
5
+
6
+ def initialize(name, params = {})
7
+ @name = name
8
+ @type = params[:type]
9
+ @label = params[:label]
10
+ @has_value = params.key? :value
11
+ @value = params[:value]
12
+ end
13
+
14
+ def has_value?
15
+ @has_value
16
+ end
17
+
18
+ def to_html
19
+ [
20
+ '<div class="form--input-group">',
21
+ label_element,
22
+ input_element,
23
+ '</div>'
24
+ ].compact.join("\n")
25
+ end
26
+
27
+ private
28
+
29
+ def label_element
30
+ str = (label || name || "").to_s
31
+ %Q(<label for="#{name}" class="form--label">#{str}</label>)
32
+ end
33
+
34
+ def input_element
35
+ _value = value ? %Q( value="#{value.to_s}") : ""
36
+ %Q(<input type="#{type.to_s}" class="form--input" id="#{name.to_s}" name="#{name.to_s}"#{_value}>)
37
+ end
38
+ end
39
+
40
+ class Form
41
+
42
+ DEFAULT_TYPE = 'application/json'
43
+
44
+ attr_accessor :resource, :name, :title, :href, :type, :self_link
45
+ attr_reader :fields
46
+
47
+ def initialize(params = {})
48
+ @name = params[:name]
49
+ @title = params[:title]
50
+ @method = params[:method] || 'POST'
51
+ @type = params[:type] || DEFAULT_TYPE
52
+ @fields = (params[:fields] || {}).map { |name, args| Field.new(name, args) }
53
+ end
54
+
55
+ def method=(m)
56
+ @method = m.to_s.upcase
57
+ end
58
+
59
+ def method
60
+ @method.to_s.upcase
61
+ end
62
+
63
+ def fields=(fields)
64
+ @fields = fields.map { |name, args| Field.new(name, args) }
65
+ end
66
+
67
+ def add_field(name, opts)
68
+ @fields << Field.new(name, opts)
69
+ end
70
+
71
+ def to_html
72
+ form_element do
73
+ [
74
+ hidden_method_element,
75
+ fields.map { |f| f.to_html }.join("\n"),
76
+ submit_element,
77
+ ].compact.join("\n")
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def form_element
84
+ [
85
+ %Q(<form class="form" method=#{method == 'GET' ? 'GET' : 'POST'}#{href ? %Q( action="#{href.to_s}") : ''}>),
86
+ block_given? ? yield : nil,
87
+ "</form>",
88
+ ].compact.join("\n")
89
+ end
90
+
91
+ def hidden_method_element
92
+ return if ['GET', 'POST'].include?(method)
93
+ %Q(<input type="hidden" name="_method" value="#{method}">)
94
+ end
95
+
96
+ def submit_element
97
+ %Q(<div class="form--input-group"><input type="submit" class="button" value="Submit"</div>)
98
+ end
99
+ end
100
+
101
+ class Builder
102
+ def self.call(block)
103
+ [
104
+ new(block, create: true).call,
105
+ new(block, edit: true).call
106
+ ]
107
+ end
108
+
109
+ attr_reader :block
110
+
111
+ def initialize(block, create: false, edit: false)
112
+ @block = block
113
+ @create = create
114
+ @edit = edit
115
+ @form = nil
116
+ @default_method = @create ? :post : :put
117
+ end
118
+
119
+ def call
120
+ instance_exec(&block)
121
+ @form
122
+ end
123
+
124
+ def form
125
+ @form ||= Form.new(method: @default_method)
126
+ end
127
+
128
+ def name(name)
129
+ form.name = name
130
+ end
131
+
132
+ def title(title)
133
+ form.title = title
134
+ end
135
+
136
+ def method(method)
137
+ form.method = method
138
+ end
139
+
140
+ def type(type)
141
+ form.type = type
142
+ end
143
+
144
+ def fields(fields)
145
+ form.fields = fields
146
+ end
147
+
148
+ def field(name, opts = {})
149
+ form.add_field(name, opts)
150
+ end
151
+
152
+ def create(&b)
153
+ return unless @create
154
+ call_nested_block(b)
155
+ end
156
+
157
+ def edit(&b)
158
+ return unless @edit
159
+ call_nested_block(b)
160
+ end
161
+
162
+ def call_nested_block(b)
163
+ old, @block = @block, b
164
+ call
165
+ ensure
166
+ @block = old
167
+ end
168
+ end
169
+
170
+ module ClassMethods
171
+ attr_reader :create_form, :edit_form
172
+
173
+ def form(&block)
174
+ @create_form, @edit_form = Builder.(block)
175
+ end
176
+ end
177
+
178
+ def self.included(base)
179
+ base.extend(ClassMethods)
180
+ end
181
+
182
+ def edit_form
183
+ self.class.edit_form.tap do |form|
184
+ form&.resource = self
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,69 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ require 'ostruct'
4
+ require 'shaf/registrable_factory'
5
+
6
+ module Shaf
7
+ module Generator
8
+ class Factory
9
+ extend RegistrableFactory
10
+ end
11
+
12
+ class Base
13
+ attr_reader :args
14
+
15
+ class << self
16
+ def inherited(child)
17
+ Factory.register(child)
18
+ end
19
+
20
+ def identifier(*ids)
21
+ @identifiers = ids.flatten
22
+ end
23
+
24
+ def usage(str = nil, &block)
25
+ @usage = str || block
26
+ end
27
+ end
28
+
29
+ def initialize(*args)
30
+ @args = args.dup
31
+ end
32
+
33
+ def template_dir
34
+ File.expand_path('../generator/templates', __FILE__)
35
+ end
36
+
37
+ def read_template(file, directory = nil)
38
+ directory ||= template_dir
39
+ filename = File.join(directory, file)
40
+ filename << ".erb" unless filename.end_with?(".erb")
41
+ File.read(filename)
42
+ end
43
+
44
+ def render(template, locals = {})
45
+ str = read_template(template)
46
+ locals[:changes] ||= []
47
+ b = OpenStruct.new(locals).instance_eval { binding }
48
+ ERB.new(str, 0, '%-<>').result(b)
49
+ rescue SystemCallError => e
50
+ puts "Failed to render template #{template}: #{e.message}"
51
+ raise
52
+ end
53
+
54
+ def write_output(file, content)
55
+ dir = File.dirname(file)
56
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
57
+ File.write(file, content)
58
+ puts "Added: #{file}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ require 'shaf/generator/controller'
65
+ require 'shaf/generator/migration'
66
+ require 'shaf/generator/model'
67
+ require 'shaf/generator/policy'
68
+ require 'shaf/generator/scaffold'
69
+ require 'shaf/generator/serializer'
@@ -0,0 +1,106 @@
1
+ module Shaf
2
+ module Generator
3
+ class Controller < Base
4
+
5
+ identifier :controller
6
+ usage 'generate controller RESOURCE_NAME [attribute:type] [..]'
7
+
8
+ def call
9
+ if name.empty?
10
+ raise Command::ArgumentError,
11
+ "Please provide a controller name when using the controller generator!"
12
+ end
13
+
14
+ create_controller
15
+ create_integration_spec
16
+ add_link_to_root
17
+ end
18
+
19
+ def name
20
+ args.first || ""
21
+ end
22
+
23
+ def params
24
+ args[1..-1].map { |param| param.split(':')}
25
+ end
26
+
27
+ def plural_name
28
+ Utils::pluralize(name)
29
+ end
30
+
31
+ def template
32
+ 'api/controller.rb'
33
+ end
34
+
35
+ def spec_template
36
+ 'spec/integration_spec.rb'
37
+ end
38
+
39
+ def target
40
+ "api/controllers/#{plural_name}_controller.rb"
41
+ end
42
+
43
+ def spec_target
44
+ "spec/integration/#{plural_name}_controller_spec.rb"
45
+ end
46
+
47
+ def create_controller
48
+ content = render(template, opts)
49
+ write_output(target, content)
50
+ end
51
+
52
+ def create_integration_spec
53
+ content = render(spec_template, opts)
54
+ write_output(spec_target, content)
55
+ end
56
+
57
+ def opts
58
+ {
59
+ name: name,
60
+ plural_name: plural_name,
61
+ serializer_class_name: "#{name.capitalize}Serializer",
62
+ model_class_name: name.capitalize,
63
+ controller_class_name: "#{plural_name.capitalize}Controller",
64
+ policy_class_name: "#{name.capitalize}Policy",
65
+ policy_file: "policies/#{name}_policy",
66
+ params: params
67
+ }
68
+ end
69
+
70
+ def add_link_to_root
71
+ file = "api/serializers/root_serializer.rb"
72
+ unless File.exist? file
73
+ puts "Warning: file '#{file}' does not exist. "\
74
+ "Not adding any link to the #{plural_name} collection"
75
+ end
76
+ added = false
77
+ content = []
78
+ File.readlines(file).reverse.each do |line|
79
+ if match = !added && line.match(/^(\s*)link /)
80
+ content.unshift link_content("#{match[1]}")
81
+ added = true
82
+ end
83
+ content.unshift(line)
84
+ end
85
+ File.open(file, 'w') { |f| f.puts content }
86
+ puts "Modified: #{file}"
87
+ end
88
+
89
+ def link_content(indentation = "")
90
+ <<~EOS.split("\n").map { |line| "#{indentation}#{line}" }
91
+
92
+ # Auto generated doc:
93
+ # Link to the collection of #{plural_name}.
94
+ # Method: GET
95
+ # Example:
96
+ # ```
97
+ # curl -H "Accept: application/json" \\
98
+ # -H "Authorization: abcdef" \\
99
+ # /#{plural_name}/5
100
+ #```
101
+ link :#{plural_name}, #{plural_name}_uri
102
+ EOS
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,122 @@
1
+ require 'date'
2
+
3
+ module Shaf
4
+ module Generator
5
+ module Migration
6
+
7
+ class Factory
8
+ extend RegistrableFactory
9
+ end
10
+
11
+ class Generator < Generator::Base
12
+ identifier :migration
13
+ usage { Factory.usage }
14
+
15
+ def call
16
+ generator = args.empty? ? Empty.new : Factory.create(*args)
17
+ (target, content) = generator.call
18
+ write_output(target, content)
19
+ rescue StandardError => e
20
+ raise Command::ArgumentError, e.message
21
+ end
22
+ end
23
+
24
+ class Base
25
+ DB_COL_TYPES = {
26
+ integer: ['Integer :%s', ':%s, Integer'],
27
+ varchar: ['String %s', ':%s, String'],
28
+ string: ['String :%s', ':%s, String'],
29
+ text: ['String :%s, text: true', ':%s, String, text: true'],
30
+ blob: ['File :%s', ':%s, File'],
31
+ bigint: ['Bignum :%s', ':%s, Bignum'],
32
+ double: ['Float :%s', ':%s, Float'],
33
+ numeric: ['BigDecimal :%s', ':%s, BigDecimal'],
34
+ date: ['Date :%s', ':%s, Date'],
35
+ timestamp: ['DateTime :%s', ':%s, DateTime'],
36
+ time: ['Time :%s', ':%s, Time'],
37
+ bool: ['TrueClass :%s', ':%s, TrueClass'],
38
+ boolean: ['TrueClass :%s', ':%s, TrueClass'],
39
+ }
40
+
41
+ attr_reader :args
42
+
43
+ class << self
44
+ def inherited(child)
45
+ Factory.register(child)
46
+ end
47
+
48
+ def identifier(*ids)
49
+ @identifiers = ids.flatten.map(&:to_s)
50
+ end
51
+
52
+ def usage(str = nil, &block)
53
+ @usage = str || block
54
+ end
55
+ end
56
+
57
+ def initialize(*args)
58
+ @args = args.dup
59
+ end
60
+
61
+ def call
62
+ validate_args
63
+ name = compile_migration_name
64
+ compile_changes
65
+ [target(name), render]
66
+ rescue StandardError => e
67
+ raise Command::ArgumentError, e.message
68
+ end
69
+
70
+ def add_change(change)
71
+ @changes ||= []
72
+ @changes << change if change
73
+ end
74
+
75
+ def db_type(type)
76
+ type ||= :string
77
+ DB_COL_TYPES[type.to_sym] or raise "Column type '#{type}' not supported"
78
+ end
79
+
80
+ def column_def(str, create: true)
81
+ name, type = str.split(':')
82
+ format db_type(type)[create ? 0 : 1], name.downcase
83
+ end
84
+
85
+ def target(name)
86
+ raise "Migration filename is nil" unless name
87
+ "db/migrations/#{timestamp}_#{name}.rb"
88
+ end
89
+
90
+ private
91
+
92
+ def timestamp
93
+ DateTime.now.strftime("%Y%m%d%H%M%S")
94
+ end
95
+
96
+ def add_timestamp_columns?
97
+ if File.exist? 'config/initializers/sequel.rb'
98
+ require 'config/initializers/sequel'
99
+ Sequel::Model.plugins.include? Sequel::Plugins::Timestamps
100
+ end
101
+ end
102
+
103
+ def render
104
+ <<~EOS
105
+ Sequel.migration do
106
+ change do
107
+ #{@changes.flatten.join("\n ")}
108
+ end
109
+ end
110
+ EOS
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
118
+ require 'shaf/generator/migration/add_column'
119
+ require 'shaf/generator/migration/create_table'
120
+ require 'shaf/generator/migration/drop_column'
121
+ require 'shaf/generator/migration/empty'
122
+ require 'shaf/generator/migration/rename_column'