shaf 0.4.1 → 0.5.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 (36) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/shaf/command/base.rb +51 -0
  4. data/lib/shaf/command/templates/Gemfile.erb +0 -1
  5. data/lib/shaf/command.rb +1 -47
  6. data/lib/shaf/extensions/resource_uris.rb +113 -151
  7. data/lib/shaf/generator/base.rb +61 -0
  8. data/lib/shaf/generator/migration/base.rb +133 -0
  9. data/lib/shaf/generator/migration/create_table.rb +4 -4
  10. data/lib/shaf/generator/migration.rb +1 -111
  11. data/lib/shaf/generator/serializer.rb +3 -51
  12. data/lib/shaf/generator/templates/api/controller.rb.erb +2 -0
  13. data/lib/shaf/generator/templates/api/policy.rb.erb +3 -2
  14. data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -6
  15. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +30 -30
  16. data/lib/shaf/generator.rb +1 -57
  17. data/lib/shaf/helpers/cache_control.rb +21 -0
  18. data/lib/shaf/helpers.rb +4 -2
  19. data/lib/shaf/rake/db.rb +1 -1
  20. data/lib/shaf/upgrade/manifest.rb +29 -8
  21. data/lib/shaf/upgrade/package.rb +51 -27
  22. data/lib/shaf/version.rb +1 -1
  23. data/templates/api/controllers/docs_controller.rb +2 -0
  24. data/templates/api/controllers/root_controller.rb +2 -3
  25. data/templates/api/policies/base_policy.rb +3 -0
  26. data/templates/api/serializers/base_serializer.rb +4 -0
  27. data/templates/api/serializers/error_serializer.rb +3 -2
  28. data/templates/api/serializers/form_serializer.rb +2 -2
  29. data/templates/api/serializers/root_serializer.rb +3 -3
  30. data/templates/config/initializers/db_migrations.rb +24 -11
  31. data/templates/config/initializers/hal_presenter.rb +0 -5
  32. data/templates/config/settings.yml +8 -3
  33. data/upgrades/0.5.0.tar.gz +0 -0
  34. data.tar.gz.sig +0 -0
  35. metadata +69 -6
  36. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 33b4f2f8ba33dcc7d3f8abe950745897fca2439e
4
- data.tar.gz: d677b6e4db2843c764027e91b00ee384a24baa60
2
+ SHA256:
3
+ metadata.gz: 7ef999e9f5ceac2a21b21e33ae805a6f576bc1363c6cbd4622cbbbdf715e8252
4
+ data.tar.gz: '0030305862359ea34e55516b2d49abcd78c20eb1393ae9d99eabcb86d1b30de4'
5
5
  SHA512:
6
- metadata.gz: 24eaf2fb8f70443206460d48853ea44d304c1616a65be60defb33147ee09e53923ee13b3563efc0ad00ce41976d7acd6048047d3b1f6d0323341fc9946900546
7
- data.tar.gz: 7714930986d85adea752deb9bd7c200a3a7f06cb10826f4e39c9b066dd96b4fd58bae7e327c8fe8bc5e276ab781a9112e8d123e2787a4676fec09a06d10954fd
6
+ metadata.gz: 3ac44a36ff0d58fce2e7fb58d9f85187dc75e1077cc801f5fcfd637738be364619c3ccbb3d5ea28b0cd46319f258d36a6047f4c22f53564487a4af8652569ffd
7
+ data.tar.gz: d4bddad299a6236d0930d861a2fdc054531f5892c29cc9c2f5c918066d6943c0aa888b9e2fa346a7733784f2311da5845c475632c4641949711a8f0784546d15
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,51 @@
1
+ require 'optparse'
2
+ require 'shaf/utils'
3
+
4
+ module Shaf
5
+ module Command
6
+ class ArgumentError < CommandError; end
7
+
8
+ class Base
9
+ include Utils
10
+
11
+ attr_reader :args, :options
12
+
13
+ class << self
14
+ def inherited(child)
15
+ Factory.register(child)
16
+ end
17
+
18
+ def identifier(*ids)
19
+ @identifiers = ids.flatten
20
+ end
21
+
22
+ def usage(str = nil, &block)
23
+ @usage = str || block
24
+ end
25
+
26
+ def exit_with_error(msg, status)
27
+ STDERR.puts msg
28
+ exit status
29
+ end
30
+
31
+ def options(option_parser, options); end
32
+ end
33
+
34
+ def initialize(*args)
35
+ @args = args.dup
36
+ @options = {}
37
+ parse_options!
38
+ end
39
+
40
+ private
41
+
42
+ def parse_options!
43
+ parser = OptionParser.new
44
+ self.class.options(parser, @options)
45
+ parser.parse!(args)
46
+ rescue OptionParser::InvalidOption => e
47
+ raise ArgumentError, e.message
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # A sample Gemfile
3
2
  source "https://rubygems.org"
4
3
  ruby '<%= RUBY_VERSION %>'
5
4
 
data/lib/shaf/command.rb CHANGED
@@ -1,62 +1,16 @@
1
- require 'optparse'
2
- require 'shaf/utils'
3
1
  require 'shaf/registrable_factory'
4
2
 
5
3
  module Shaf
6
4
  module Command
7
-
8
5
  class CommandError < StandardError; end
9
- class ArgumentError < CommandError; end
10
6
 
11
7
  class Factory
12
8
  extend RegistrableFactory
13
9
  end
14
-
15
- class Base
16
- include Utils
17
-
18
- attr_reader :args, :options
19
-
20
- class << self
21
- def inherited(child)
22
- Factory.register(child)
23
- end
24
-
25
- def identifier(*ids)
26
- @identifiers = ids.flatten
27
- end
28
-
29
- def usage(str = nil, &block)
30
- @usage = str || block
31
- end
32
-
33
- def exit_with_error(msg, status)
34
- STDERR.puts msg
35
- exit status
36
- end
37
-
38
- def options(option_parser, options); end
39
- end
40
-
41
- def initialize(*args)
42
- @args = args.dup
43
- @options = {}
44
- parse_options!
45
- end
46
-
47
- private
48
-
49
- def parse_options!
50
- parser = OptionParser.new
51
- self.class.options(parser, @options)
52
- parser.parse!(args)
53
- rescue OptionParser::InvalidOption => e
54
- raise ArgumentError, e.message
55
- end
56
- end
57
10
  end
58
11
  end
59
12
 
13
+ require 'shaf/command/base'
60
14
  require 'shaf/command/new'
61
15
  require 'shaf/command/upgrade'
62
16
  require 'shaf/command/server'
@@ -9,12 +9,7 @@ module Shaf
9
9
  end
10
10
 
11
11
  def register_uri(name, uri)
12
- if UriHelper.respond_to? MethodBuilder.method_name(name)
13
- raise "resource uri #{name} can't be registered. Method already exist!"
14
- end
15
- method_string = MethodBuilder.as_string(name, uri)
16
- UriHelperMethods.eval_method(method_string)
17
- UriHelperMethods.register(MethodBuilder.template_method_name(name)) { uri.dup.freeze }
12
+ MethodBuilder.new(name, uri).call
18
13
 
19
14
  include UriHelper unless self < UriHelper
20
15
  end
@@ -39,6 +34,13 @@ module Shaf
39
34
  def self.included(mod)
40
35
  mod.extend self
41
36
  end
37
+
38
+ def self.base_uri
39
+ protocol = Settings.protocol || 'http'
40
+ host = Settings.host || 'localhost'
41
+ port = Settings.port ? ":#{Settings.port}" : ""
42
+ "#{protocol}://#{host}#{port}"
43
+ end
42
44
  end
43
45
 
44
46
  # This class register uri helper methods like:
@@ -55,52 +57,10 @@ module Shaf
55
57
  #
56
58
  class CreateUriMethods
57
59
 
58
- # Resources should never be nested more than 1 level deep.
59
- MAX_NESTING_DEPTH = 1
60
-
61
- class UriTemplateNestingError < StandardError
62
- def initialize(msg = nil)
63
- msg ||= "Uri templates only supports a nesting depth of #{MAX_NESTING_DEPTH}"
64
- super(msg)
65
- end
66
- end
67
-
68
- class UriTemplateVariableError < StandardError
69
- def initialize(msg = nil)
70
- msg ||= "Mismatch between uri templates and resources"
71
- super(msg)
72
- end
73
- end
74
-
75
- class << self
76
- def resource_helper_uri(template_uri, *resources, query)
77
- uri = replace_templates(template_uri, *resources)
78
- query_string = MethodBuilder.query_string(query)
79
- "#{uri}#{query_string}".freeze
80
- end
81
-
82
- def replace_templates(template_uri, *resources)
83
- symbols = MethodBuilder.extract_symbols(template_uri)
84
- resources.compact!
85
- raise UriTemplateVariableError if symbols.size != resources.size
86
-
87
- MethodBuilder.transform_symbols(template_uri) do |segment|
88
- resrc = resources.shift
89
- sym = symbols.shift
90
- resrc.respond_to?(sym) ? resrc.public_send(sym) : resrc
91
- end
92
- end
93
- end
94
-
95
60
  def initialize(name, base: nil, plural_name: nil)
96
61
  @name = name.to_s
97
62
  @base = base&.sub(%r(/\Z), '') || ''
98
63
  @plural_name = plural_name&.to_s || Utils::pluralize(name.to_s)
99
-
100
- if nesting_depth > MAX_NESTING_DEPTH
101
- raise UriTemplateNestingError,
102
- "Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{@base}"
103
- end
104
64
  end
105
65
 
106
66
  def call
@@ -120,14 +80,12 @@ module Shaf
120
80
 
121
81
  def register_resources_uri
122
82
  template_uri = "#{base}/#{plural_name}".freeze
123
- helper_name = "#{plural_name}_uri".freeze
124
- register(template_uri, helper_name)
83
+ register(plural_name, template_uri)
125
84
  end
126
85
 
127
86
  def register_resource_uri
128
87
  template_uri = "#{base}/#{plural_name}/:id".freeze
129
- helper_name = "#{name}_uri".freeze
130
- register(template_uri, helper_name)
88
+ register(name, template_uri)
131
89
  end
132
90
 
133
91
  # If a resource has the same singular and plural names, then this method
@@ -136,137 +94,141 @@ module Shaf
136
94
  def register_resource_uri_by_arg
137
95
  resource_template_uri = "#{base}/#{plural_name}/:id"
138
96
  collection_template_uri = "#{base}/#{plural_name}"
139
- helper_name = "#{plural_name}_uri"
140
97
 
141
- block = resource_or_collection_method_proc(resource_template_uri, collection_template_uri)
142
- UriHelperMethods.register(helper_name, &block)
143
- UriHelperMethods.register("#{helper_name}_template") do |collection = false|
144
- (collection ? collection_template_uri : resource_template_uri).freeze
145
- end
98
+ MethodBuilder.new(name, resource_template_uri, alt_uri: collection_template_uri).call
146
99
  end
147
100
 
148
101
  def register_new_resource_uri
149
102
  template_uri = "#{base}/#{plural_name}/form".freeze
150
- helper_name = "new_#{name}_uri".freeze
151
- register(template_uri, helper_name)
103
+ register("new_#{name}", template_uri)
152
104
  end
153
105
 
154
106
  def register_edit_resource_uri
155
107
  template_uri = "#{base}/#{plural_name}/:id/edit".freeze
156
- helper_name = "edit_#{name}_uri".freeze
157
- register(template_uri, helper_name)
108
+ register("edit_#{name}", template_uri)
158
109
  end
159
110
 
160
- def register(template_uri, helper_name)
161
- block = resource_helper_method_proc(template_uri)
162
- UriHelperMethods.register(helper_name, &block)
163
- UriHelperMethods.register("#{helper_name}_template") { template_uri }
111
+ def register(name, template_uri)
112
+ MethodBuilder.new(name, template_uri).call
164
113
  end
114
+ end
165
115
 
166
- def resource_helper_method_proc(template_uri)
167
- arg_count = MethodBuilder.extract_symbols(template_uri).size
116
+ class MethodBuilder
117
+ def self.query_string(query)
118
+ return "" unless query.any?
119
+ "?#{query.map { |key,value| "#{key}=#{value}" }.join("&")}"
120
+ end
168
121
 
169
- case arg_count
170
- when 0
171
- ->(**query) do
172
- CreateUriMethods.resource_helper_uri(template_uri, query)
173
- end
174
- when 1
175
- ->(resrc, **query) do
176
- CreateUriMethods.resource_helper_uri(template_uri, resrc, query)
177
- end
178
- when 2
179
- ->(parent_resrc, resrc, **query) do
180
- CreateUriMethods.resource_helper_uri(template_uri, parent_resrc, resrc, query)
181
- end
182
- else
183
- raise UriTemplateNestingError,
184
- "Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{template_uri}"
185
- end
122
+ def initialize(name, uri, alt_uri: nil)
123
+ @name = name
124
+ @uri = uri
125
+ @alt_uri = alt_uri
186
126
  end
187
127
 
188
- def resource_or_collection_method_proc(resource_template_uri, collection_template_uri)
189
- case nesting_depth
190
- when 0
191
- ->(resrc = nil, **query) {
192
- args = if resrc.nil?
193
- [collection_template_uri, query]
194
- else
195
- [resource_template_uri, resrc, query]
196
- end
197
- CreateUriMethods.resource_helper_uri(*args)
198
- }
199
- when 1
200
- ->(parent_resrc, resrc = nil, **query) {
201
- args = if resrc.nil?
202
- [collection_template_uri, parent_resrc, query]
203
- else
204
- [resource_template_uri, parent_resrc, resrc, query]
205
- end
206
- CreateUriMethods.resource_helper_uri(*args)
207
- }
128
+ def call
129
+ if UriHelper.respond_to? method_name
130
+ raise "resource uri #{@name} can't be registered. " \
131
+ "Method :#{method_name} already exist!"
132
+ end
133
+
134
+ if @alt_uri.nil?
135
+ build_methods
208
136
  else
209
- raise UriTemplateNestingError,
210
- "Too deep nesting level (max #{MAX_NESTING_DEPTH}): #{template_uri}"
137
+ build_methods_with_optional_arg
211
138
  end
212
139
  end
213
140
 
214
- def nesting_depth
215
- MethodBuilder.extract_symbols(base).size
141
+ private
142
+
143
+ def build_methods
144
+ UriHelperMethods.eval_method method_string
145
+ UriHelperMethods.register(template_method_name, &template_proc)
216
146
  end
217
- end
218
147
 
219
- module MethodBuilder
220
- class << self
221
- def method_name(name)
222
- "#{name}_uri"
223
- end
148
+ def build_methods_with_optional_arg
149
+ UriHelperMethods.eval_method method_with_optional_arg_string
150
+ UriHelperMethods.register(template_method_name, &template_proc)
151
+ end
224
152
 
225
- def template_method_name(name)
226
- "#{method_name(name)}_template"
227
- end
153
+ def method_name
154
+ "#{@name}_uri".freeze
155
+ end
228
156
 
229
- def signature(name, uri)
230
- args = extract_symbols(uri)
231
- s = "#{method_name(name)}("
232
- s << (args.empty? ? "**query)" : "#{args.join(', ')}, **query)")
233
- end
157
+ def template_method_name
158
+ "#{method_name}_template".freeze
159
+ end
234
160
 
235
- def query_string(query)
236
- return "" unless query.any?
237
- "?#{query.map { |key,value| "#{key}=#{value}" }.join("&")}"
238
- end
161
+ def signature(optional_args: 0)
162
+ s = "#{method_name}("
239
163
 
240
- def as_string(name, uri)
241
- signature = signature(name, uri)
242
- <<~Ruby
243
- def #{signature}
244
- query_string = MethodBuilder.query_string(query)
245
- \"#{interpolated_uri_string(uri)}\#{query_string}\".freeze
246
- end
247
- Ruby
248
- end
164
+ symbols = extract_symbols.size.times.map { |i| "arg#{i}" }
165
+ sym_count = symbols.size
249
166
 
250
- def extract_symbols(uri)
251
- uri.split('/').grep(/:.*/).map { |t| t[1..-1] }.map(&:to_sym)
167
+ args = []
168
+ symbols.each_with_index do |arg, i|
169
+ if i < sym_count - optional_args
170
+ args << "arg#{i}"
171
+ else
172
+ args << "arg#{i} = nil"
173
+ end
252
174
  end
175
+ s << (args.empty? ? "**query)" : "#{args.join(', ')}, **query)")
176
+ end
253
177
 
254
- def transform_symbols(uri)
255
- uri.split('/').map do |segment|
256
- next segment unless segment.start_with? ':'
257
- yield segment
258
- end.join('/')
259
- end
178
+ def method_string
179
+ base_uri = UriHelper.base_uri
180
+ <<~Ruby
181
+ def #{signature}
182
+ query_str = Shaf::MethodBuilder.query_string(query)
183
+ \"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
184
+ end
185
+ Ruby
186
+ end
187
+
188
+ def method_with_optional_arg_string
189
+ base_uri = UriHelper.base_uri
190
+ arg_no = extract_symbols.size - 1
191
+ <<~Ruby
192
+ def #{signature(optional_args: 1)}
193
+ query_str = Shaf::MethodBuilder.query_string(query)
194
+ if arg#{arg_no}.nil?
195
+ \"#{base_uri}#{interpolated_uri_string(@alt_uri)}\#{query_str}\".freeze
196
+ else
197
+ \"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
198
+ end
199
+ end
200
+ Ruby
201
+ end
202
+
203
+ def extract_symbols(uri = @uri)
204
+ uri.split('/').grep(/:.*/).map { |t| t[1..-1].to_sym }
205
+ end
260
206
 
261
- private
207
+ def transform_symbols(uri)
208
+ i = -1
209
+ uri.split('/').map do |segment|
210
+ next segment unless segment.start_with? ':'
211
+ i += 1
212
+ yield segment, i
213
+ end.join('/')
214
+ end
262
215
 
263
- def interpolated_uri_string(uri)
264
- return uri if uri == '/'
216
+ def interpolated_uri_string(uri)
217
+ return uri if uri == '/'
265
218
 
266
- transform_symbols(uri) do |segment|
267
- str = segment[1..-1]
268
- "\#{#{str}.respond_to?(#{segment}) ? #{str}.#{str} : #{str}}"
269
- end
219
+ transform_symbols(uri) do |segment, i|
220
+ sym = segment[1..-1]
221
+ "\#{arg#{i}.respond_to?(#{segment}) ? arg#{i}.#{sym} : arg#{i}}"
222
+ end
223
+ end
224
+
225
+ def template_proc
226
+ uri, alt_uri = @uri, @alt_uri
227
+
228
+ if alt_uri.nil?
229
+ -> { uri.freeze }
230
+ else
231
+ ->(collection = false) { collection ? alt_uri : uri }
270
232
  end
271
233
  end
272
234
  end
@@ -0,0 +1,61 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ require 'ostruct'
4
+
5
+ module Shaf
6
+ module Generator
7
+ class Base
8
+ attr_reader :args
9
+
10
+ class << self
11
+ def inherited(child)
12
+ Factory.register(child)
13
+ end
14
+
15
+ def identifier(*ids)
16
+ @identifiers = ids.flatten
17
+ end
18
+
19
+ def usage(str = nil, &block)
20
+ @usage = str || block
21
+ end
22
+
23
+ def options(option_parser, options); end
24
+ end
25
+
26
+ def initialize(*args)
27
+ @args = args.dup
28
+ end
29
+
30
+ def call(options = {}); end
31
+
32
+ def template_dir
33
+ File.expand_path('../templates', __FILE__)
34
+ end
35
+
36
+ def read_template(file, directory = nil)
37
+ directory ||= template_dir
38
+ filename = File.join(directory, file)
39
+ filename << ".erb" unless filename.end_with?(".erb")
40
+ File.read(filename)
41
+ end
42
+
43
+ def render(template, locals = {})
44
+ str = read_template(template)
45
+ locals[:changes] ||= []
46
+ b = OpenStruct.new(locals).instance_eval { binding }
47
+ ERB.new(str, 0, '%-<>').result(b)
48
+ rescue SystemCallError => e
49
+ puts "Failed to render template #{template}: #{e.message}"
50
+ raise
51
+ end
52
+
53
+ def write_output(file, content)
54
+ dir = File.dirname(file)
55
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
56
+ File.write(file, content)
57
+ puts "Added: #{file}"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,133 @@
1
+ require 'date'
2
+
3
+ module Shaf
4
+ module Generator
5
+ module Migration
6
+ class Base
7
+ DB_COL_FORMAT_STRINGS = {
8
+ integer: ['Integer :%s', 'add_column :%s, Integer'],
9
+ varchar: ['String %s', 'add_column :%s, String'],
10
+ string: ['String :%s', 'add_column :%s, String'],
11
+ text: ['String :%s, text: true', 'add_column :%s, String, text: true'],
12
+ blob: ['File :%s', 'add_column :%s, File'],
13
+ bigint: ['Bignum :%s', 'add_column :%s, Bignum'],
14
+ double: ['Float :%s', 'add_column :%s, Float'],
15
+ numeric: ['BigDecimal :%s', 'add_column :%s, BigDecimal'],
16
+ date: ['Date :%s', 'add_column :%s, Date'],
17
+ timestamp: ['DateTime :%s', 'add_column :%s, DateTime'],
18
+ time: ['Time :%s', 'add_column :%s, Time'],
19
+ bool: ['TrueClass :%s', 'add_column :%s, TrueClass'],
20
+ boolean: ['TrueClass :%s', 'add_column :%s, TrueClass'],
21
+ index: ['index :%s, unique: true', 'add_index :%s'],
22
+ }
23
+
24
+ COMPLEX_DB_TYPES = [
25
+ {
26
+ pattern: /\Aforeign_key\((\w+)\)/,
27
+ strings: ['foreign_key :%s, :\1', 'add_foreign_key :%s, :\1'],
28
+ validator: ->(type, match) {
29
+ break if ::DB.table_exists?(match[1])
30
+ ["Foreign key table '#{match[1]}' does not exist!"]
31
+ }
32
+ }
33
+ ]
34
+
35
+ attr_reader :args
36
+
37
+ class << self
38
+ def inherited(child)
39
+ Factory.register(child)
40
+ end
41
+
42
+ def identifier(*ids)
43
+ @identifiers = ids.flatten.map(&:to_s)
44
+ end
45
+
46
+ def usage(str = nil, &block)
47
+ @usage = str || block
48
+ end
49
+ end
50
+
51
+ def initialize(*args)
52
+ @args = args.dup
53
+ end
54
+
55
+ def call
56
+ validate_args
57
+ name = compile_migration_name
58
+ compile_changes
59
+ [target(name), render]
60
+ rescue StandardError => e
61
+ raise Command::ArgumentError, e.message
62
+ end
63
+
64
+ def add_change(change)
65
+ @changes ||= []
66
+ @changes << change if change
67
+ end
68
+
69
+ def column_def(str, create: true)
70
+ name, type = str.split(':')
71
+ format_string = db_format_string(type, create ? 0 : 1)
72
+ format format_string, name.downcase
73
+ end
74
+
75
+ def target(name)
76
+ raise "Migration filename is nil" unless name
77
+ "db/migrations/#{timestamp}_#{name}.rb"
78
+ end
79
+
80
+ private
81
+
82
+ def timestamp
83
+ DateTime.now.strftime("%Y%m%d%H%M%S")
84
+ end
85
+
86
+ def add_timestamp_columns?
87
+ if File.exist? 'config/initializers/sequel.rb'
88
+ require 'config/initializers/sequel'
89
+ Sequel::Model.plugins.include? Sequel::Plugins::Timestamps
90
+ end
91
+ end
92
+
93
+ def db_format_string(type, range = 0..1)
94
+ type ||= :string
95
+ result = DB_COL_FORMAT_STRINGS[type.to_sym]
96
+ result ||= regexp_db_format_string(type)
97
+ raise "Column type '#{type}' not supported" unless result
98
+ result[range]
99
+ end
100
+
101
+ def regexp_db_format_string(type)
102
+ COMPLEX_DB_TYPES.each do |complex_type|
103
+ match = complex_type[:pattern].match(type) or next
104
+ validator = complex_type[:validator]
105
+ errors = validator&.call(type, match)
106
+ if errors.nil? || errors.empty?
107
+ return complex_type[:strings].map { |s| replace_backreferences(match, s) }
108
+ end
109
+ raise "Failed to process '#{type}': #{errors.join(', ')}"
110
+ end
111
+ end
112
+
113
+ def replace_backreferences(match, str)
114
+ groups = match.size
115
+ (1...groups).inject(str) do |s, i|
116
+ s.gsub("\\#{i}", match[i])
117
+ end
118
+ end
119
+
120
+ def render
121
+ <<~EOS
122
+ Sequel.migration do
123
+ change do
124
+ #{@changes.flatten.join("\n ")}
125
+ end
126
+ end
127
+ EOS
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -11,14 +11,14 @@ module Shaf
11
11
  raise "Please provide a table name when generation a create table migration"
12
12
  end
13
13
 
14
- def compile_migration_name
15
- "create_#{table_name}_table"
16
- end
17
-
18
14
  def table_name
19
15
  args.first || ""
20
16
  end
21
17
 
18
+ def compile_migration_name
19
+ "create_#{table_name}_table"
20
+ end
21
+
22
22
  def compile_changes
23
23
  add_change create_table_change
24
24
  end