roda-project 0.1.9 → 0.1.11

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bca56b06faeb347730f67dce564950b0f3fb9a2e0271820551f96752678121e1
4
- data.tar.gz: 1233a11fa1418c2492e956e79ea8d2f74db2689ce902e3fad04159245fc8a451
3
+ metadata.gz: 3fceab0441f37da52ca87b8c028d4fa72573854e2a1adc25f34f76ac6e7b4e2a
4
+ data.tar.gz: 24c9c54923283bee8394d0597206feed0d1a914cbf30caa1368bef696a2ffa3a
5
5
  SHA512:
6
- metadata.gz: 0f42c3f7152377f79986fbba0fb2c2cfa7cb6c583c68ca05c9f78463c44e8445ad9c76a87e506302718f8d07c1ea97b195359878b21357b89b005e329de45e35
7
- data.tar.gz: 23ce0cb9af858542d5a213e5e934a97619ea3b4be93b3379b06a04b63dabaa2019d18cc5e4fcf847581bad81a1c9de4eed81efa660367827b6627cbee57a6158
6
+ metadata.gz: 7aa0cc8165bd3a515e8e06a625e0621ffd9257fa276431fd1babfd2ba1f3a4fd8336dc595c4a56d41f09c18505a8809870e1369612e46c810204ad65d9511c71
7
+ data.tar.gz: 9cf9d5bda0e6f992d820b20ec16647df888bcf36246500669a828efac54b1c091ddf73d628a54706af50bdd9b466b027076765755e74d00aa4fee6ea05fdf06b
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Roda
4
+ module Project
5
+ module Bin
6
+ class Generators < ::Thor
7
+ class Migration
8
+ class ActionDetector
9
+ include Roda::Project::Helpers::Inflections
10
+
11
+ attr_reader :name, :args
12
+
13
+ def initialize(name, args: [])
14
+ @name = name.to_s
15
+ @args = args || []
16
+ end
17
+
18
+ def detect
19
+ camel_name = camelize(name)
20
+
21
+ if (m = camel_name.match(/^CreateJoinTable(?<t1>[A-Z][a-zA-Z0-9]*?)(?<t2>[A-Z][a-zA-Z0-9]*)$/)) ||
22
+ (m = camel_name.match(/JoinTable(?<t1>[A-Z][a-zA-Z0-9]*?)(?<t2>[A-Z][a-zA-Z0-9]*)$/)) ||
23
+ camel_name.start_with?("CreateJoinTable") || camel_name.include?("JoinTable")
24
+ tables = parse_join_tables(m)
25
+ {action: :create_join_table, tables: tables}
26
+ elsif (m = camel_name.match(/^Create(?<table_name>[A-Z0-9].*)$/))
27
+ {action: :create_table, table_name: pluralize(underscore(m[:table_name]))}
28
+ elsif (m = camel_name.match(/^Add.+To(?<table_name>[A-Z0-9].*)$/))
29
+ {action: :add_columns, table_name: pluralize(underscore(m[:table_name]))}
30
+ elsif (m = camel_name.match(/^Remove.+From(?<table_name>[A-Z0-9].*)$/))
31
+ {action: :drop_columns, table_name: pluralize(underscore(m[:table_name]))}
32
+ else
33
+ {action: :generic, table_name: nil}
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def parse_join_tables(match)
40
+ if args.size >= 2
41
+ args[0..1].map do |arg|
42
+ clean = underscore(arg.to_s.gsub(/:.*$/, ""))
43
+ s_clean = singularize(clean)
44
+ p_clean = pluralize(s_clean)
45
+ ["#{s_clean}_id", p_clean]
46
+ end
47
+ elsif match
48
+ raw1 = underscore(match[:t1])
49
+ raw2 = underscore(match[:t2])
50
+ s1 = singularize(raw1)
51
+ s2 = singularize(raw2)
52
+ t1 = pluralize(s1)
53
+ t2 = pluralize(s2)
54
+ [["#{s1}_id", t1], ["#{s2}_id", t2]]
55
+ else
56
+ []
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Roda
4
+ module Project
5
+ module Bin
6
+ class Generators
7
+ class Migration
8
+ class CodeBuilder
9
+ attr_reader :detection, :fields
10
+
11
+ def initialize(detection:, fields:)
12
+ @detection = detection || {}
13
+ @fields = fields || []
14
+ end
15
+
16
+ def build
17
+ action = detection[:action]
18
+
19
+ case action
20
+ when :create_table
21
+ build_create_table
22
+ when :add_columns
23
+ build_add_columns
24
+ when :drop_columns
25
+ build_drop_columns
26
+ when :create_join_table
27
+ build_create_join_table
28
+ else
29
+ build_generic
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def build_create_table
36
+ table = detection[:table_name]
37
+ lines = []
38
+ lines << "Sequel.migration do"
39
+ lines << " change do"
40
+ lines << " create_table(:#{table}) do"
41
+ lines << " primary_key :id"
42
+
43
+ indexes = []
44
+
45
+ fields.each do |field|
46
+ if field[:is_reference]
47
+ ref_line = " foreign_key :#{field[:name]}, :#{field[:target_table]}"
48
+ ref_line += format_options(field[:options]) unless field[:options].empty?
49
+ lines << ref_line
50
+ indexes << " index :#{field[:name]}" if field[:index]
51
+ else
52
+ col_line = " #{field[:type]} :#{field[:name]}"
53
+ col_line += format_options(field[:options]) unless field[:options].empty?
54
+ lines << col_line
55
+
56
+ if field[:index] == :unique
57
+ indexes << " index :#{field[:name]}, unique: true"
58
+ elsif field[:index]
59
+ indexes << " index :#{field[:name]}"
60
+ end
61
+
62
+ if field[:composite_index]
63
+ cols_str = field[:composite_index].map { |c| ":#{c}" }.join(", ")
64
+ indexes << " index [#{cols_str}]"
65
+ end
66
+ end
67
+ end
68
+
69
+ lines << " DateTime :created_at"
70
+ lines << " DateTime :updated_at"
71
+
72
+ unless indexes.empty?
73
+ lines << ""
74
+ lines.concat(indexes)
75
+ end
76
+
77
+ lines << " end"
78
+ lines << " end"
79
+ lines << "end"
80
+ lines.join("\n") + "\n"
81
+ end
82
+
83
+ def build_add_columns
84
+ table = detection[:table_name]
85
+ lines = []
86
+ lines << "Sequel.migration do"
87
+ lines << " change do"
88
+ lines << " alter_table(:#{table}) do"
89
+
90
+ fields.each do |field|
91
+ if field[:is_reference]
92
+ ref_line = " add_foreign_key :#{field[:name]}, :#{field[:target_table]}"
93
+ ref_line += format_options(field[:options]) unless field[:options].empty?
94
+ lines << ref_line
95
+ lines << " add_index :#{field[:name]}" if field[:index]
96
+ else
97
+ col_line = " add_column :#{field[:name]}, #{field[:type]}"
98
+ col_line += format_options(field[:options]) unless field[:options].empty?
99
+ lines << col_line
100
+
101
+ if field[:index] == :unique
102
+ lines << " add_index :#{field[:name]}, unique: true"
103
+ elsif field[:index]
104
+ lines << " add_index :#{field[:name]}"
105
+ end
106
+
107
+ if field[:composite_index]
108
+ cols_str = field[:composite_index].map { |c| ":#{c}" }.join(", ")
109
+ lines << " add_index [#{cols_str}]"
110
+ end
111
+ end
112
+ end
113
+
114
+ lines << " end"
115
+ lines << " end"
116
+ lines << "end"
117
+ lines.join("\n") + "\n"
118
+ end
119
+
120
+ def build_drop_columns
121
+ table = detection[:table_name]
122
+ lines = []
123
+ lines << "Sequel.migration do"
124
+ lines << " change do"
125
+ lines << " alter_table(:#{table}) do"
126
+
127
+ fields.each do |field|
128
+ lines << " drop_column :#{field[:name]}"
129
+ end
130
+
131
+ lines << " end"
132
+ lines << " end"
133
+ lines << "end"
134
+ lines.join("\n") + "\n"
135
+ end
136
+
137
+ def build_create_join_table
138
+ tables = detection[:tables] || []
139
+ args_str = tables.map { |fk, tbl| "#{fk}: :#{tbl}" }.join(", ")
140
+
141
+ lines = []
142
+ lines << "Sequel.migration do"
143
+ lines << " change do"
144
+ lines << " create_join_table(#{args_str})"
145
+ lines << " end"
146
+ lines << "end"
147
+ lines.join("\n") + "\n"
148
+ end
149
+
150
+ def build_generic
151
+ <<~RUBY
152
+ Sequel.migration do
153
+ up do
154
+ # add your migration here
155
+ end
156
+
157
+ down do
158
+ # remove your migration here
159
+ end
160
+ end
161
+ RUBY
162
+ end
163
+
164
+ def format_options(opts)
165
+ return "" if opts.empty?
166
+
167
+ formatted = opts.map do |k, v|
168
+ val_str = case v
169
+ when Array
170
+ "[#{v.join(", ")}]"
171
+ when Symbol
172
+ ":#{v}"
173
+ else
174
+ v.inspect
175
+ end
176
+ "#{k}: #{val_str}"
177
+ end.join(", ")
178
+
179
+ ", #{formatted}"
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Roda
4
+ module Project
5
+ module Bin
6
+ class Generators
7
+ class Migration
8
+ class FieldParser
9
+ include Roda::Project::Helpers::Inflections
10
+
11
+ TYPE_MAP = {
12
+ "string" => "String",
13
+ "text" => "String",
14
+ "integer" => "Integer",
15
+ "bigint" => "Bignum",
16
+ "float" => "Float",
17
+ "decimal" => "BigDecimal",
18
+ "datetime" => "DateTime",
19
+ "timestamp" => "DateTime",
20
+ "time" => "Time",
21
+ "date" => "Date",
22
+ "boolean" => "TrueClass",
23
+ "binary" => "File",
24
+ "json" => ":json",
25
+ "jsonb" => ":jsonb",
26
+ "uuid" => ":uuid"
27
+ }.freeze
28
+
29
+ attr_reader :raw_fields
30
+
31
+ def initialize(raw_fields)
32
+ @raw_fields = raw_fields || []
33
+ end
34
+
35
+ def parse
36
+ fields = []
37
+
38
+ raw_fields.each do |raw_field|
39
+ parts = raw_field.to_s.split(":")
40
+ next if parts.empty?
41
+
42
+ name = parts[0]
43
+ type_part = parts[1] || "string"
44
+ index_part = parts[2]
45
+
46
+ # Extract modifier e.g. string{50}, decimal{10.2}, references{polymorphic}
47
+ type, modifier = extract_type_and_modifier(type_part)
48
+
49
+ if type == "references" || type == "belongs_to"
50
+ if modifier == "polymorphic"
51
+ fields << {
52
+ name: "#{name}_id",
53
+ type: "Bignum",
54
+ options: {},
55
+ index: false,
56
+ composite_index: ["#{name}_type", "#{name}_id"]
57
+ }
58
+ fields << {
59
+ name: "#{name}_type",
60
+ type: "String",
61
+ options: {},
62
+ index: false
63
+ }
64
+ else
65
+ singular_name = singularize(name)
66
+ plural_table = pluralize(singular_name)
67
+ fields << {
68
+ name: "#{name}_id",
69
+ is_reference: true,
70
+ target_table: plural_table,
71
+ index: true,
72
+ options: {}
73
+ }
74
+ end
75
+ else
76
+ sequel_type = TYPE_MAP[type] || "String"
77
+ options = {}
78
+
79
+ options[:text] = true if type == "text"
80
+
81
+ if modifier
82
+ if modifier.include?(".")
83
+ prec, scale = modifier.split(".").map(&:to_i)
84
+ options[:size] = [prec, scale]
85
+ elsif /^\d+$/.match?(modifier)
86
+ options[:size] = modifier.to_i
87
+ end
88
+ end
89
+
90
+ index_val = parse_index_spec(index_part)
91
+
92
+ fields << {
93
+ name: name,
94
+ type: sequel_type,
95
+ options: options,
96
+ index: index_val
97
+ }
98
+ end
99
+ end
100
+
101
+ fields
102
+ end
103
+
104
+ private
105
+
106
+ def extract_type_and_modifier(type_part)
107
+ if type_part =~ /^(.*?)\{(.*?)\}$/
108
+ [Regexp.last_match(1), Regexp.last_match(2)]
109
+ else
110
+ [type_part, nil]
111
+ end
112
+ end
113
+
114
+ def parse_index_spec(index_part)
115
+ case index_part
116
+ when "uniq", "unique"
117
+ :unique
118
+ when "index"
119
+ true
120
+ else
121
+ false
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,4 +1,5 @@
1
- # rubocop:disable Layout/HeredocIndentation
1
+ # frozen_string_literal: true
2
+
2
3
  class Roda
3
4
  module Project
4
5
  module Bin
@@ -10,6 +11,7 @@ class Roda
10
11
  exit 1
11
12
  end
12
13
 
14
+ puts "* creating migration"
13
15
  FileUtils.mkdir_p(migrations_path) unless File.directory?(migrations_path)
14
16
 
15
17
  existing_migrations = Dir.glob(File.join(migrations_path, "*.rb"))
@@ -18,22 +20,16 @@ class Roda
18
20
  end.max || 0
19
21
  next_number = (max_number + 1).to_s.rjust(3, "0")
20
22
 
21
- filename = File.join(migrations_path, "#{next_number}_#{migration_name}.rb")
22
-
23
- content = <<~RUBY
24
- Sequel.migration do
25
- up do
26
- # add your migration here
27
- end
23
+ formatted_name = underscore(migration_name)
24
+ filename = File.join(migrations_path, "#{next_number}_#{formatted_name}.rb")
28
25
 
29
- down do
30
- # remove your migration here
31
- end
32
- end
33
- RUBY
26
+ detection = ActionDetector.new(migration_name, args: field_args).detect
27
+ fields = FieldParser.new(field_args).parse
28
+ content = CodeBuilder.new(detection: detection, fields: fields).build
34
29
 
35
30
  File.write(filename, content)
36
- puts "* created migration: #{filename}"
31
+
32
+ puts_create_message(filename)
37
33
  end
38
34
 
39
35
  def migrations_path
@@ -43,9 +39,21 @@ class Roda
43
39
  def migration_name
44
40
  @migration_name ||= @args[0]
45
41
  end
42
+
43
+ def field_args
44
+ @field_args ||= @args[1..] || []
45
+ end
46
+
47
+ private
48
+
49
+ def underscore(str)
50
+ str.to_s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
51
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
52
+ .tr("-", "_")
53
+ .downcase
54
+ end
46
55
  end
47
56
  end
48
57
  end
49
58
  end
50
59
  end
51
- # rubocop:enable Layout/HeredocIndentation
@@ -0,0 +1,119 @@
1
+ class Roda
2
+ module Project
3
+ module Bin
4
+ class Generators < ::Thor
5
+ class Model < Roda::Project::Bin::Generator
6
+ include Roda::Project::Helpers::Inflections
7
+
8
+ def call
9
+ if model_name.nil? || field_args == []
10
+ puts "Usage: bin/roda g model name field1 field2"
11
+ exit 1
12
+ end
13
+
14
+ puts "* creating model"
15
+ create_model
16
+ create_model_test
17
+ Migration.new(args: [migration_name].concat(field_args)).call
18
+ end
19
+
20
+ def create_model_test
21
+ ensure_and_get_path("spec/app/models", model_relative_path)
22
+ filename = File.join("spec/app/models", *name_segments[0..-2], "#{model_file_basename}_spec.rb")
23
+ File.write(filename, spec_code)
24
+
25
+ puts_create_message(filename)
26
+ end
27
+
28
+ def create_model
29
+ ensure_and_get_path("app/models", model_relative_path)
30
+ filename = File.join("app/models", *name_segments[0..-2], "#{model_file_basename}.rb")
31
+ File.write(filename, code)
32
+
33
+ puts_create_message(filename)
34
+ end
35
+
36
+ def spec_code
37
+ depth = 2 + name_segments.length - 1
38
+ relative_prefix = "../" * depth
39
+ <<~RUBY
40
+ require_relative "#{relative_prefix}spec_helper"
41
+
42
+ describe #{qualified_class_name} do
43
+ end
44
+ RUBY
45
+ end
46
+
47
+ def code
48
+ if name_segments.length == 1
49
+ <<~RUBY
50
+ class #{qualified_class_name}
51
+ end
52
+ RUBY
53
+ else
54
+ depth = name_segments.length - 1
55
+ class_indent = " " * depth
56
+ lines = [
57
+ "#{class_indent}class #{camelize(name_segments.last)} < Sequel::Model(:#{table_name})",
58
+ "#{class_indent}end"
59
+ ]
60
+
61
+ name_segments[0..-2].map { |s| camelize(s) }.reverse.each_with_index do |mod, idx|
62
+ mod_indent = " " * (depth - 1 - idx)
63
+ lines = ["#{mod_indent}class #{mod}"] + lines + ["#{mod_indent}end"]
64
+ end
65
+
66
+ lines.join("\n") + "\n"
67
+ end
68
+ end
69
+
70
+ def migration_name
71
+ segs = name_segments.map { |s| camelize(s) }
72
+ segs[-1] = pluralize(segs[-1])
73
+ "Create" + segs.join("_")
74
+ end
75
+
76
+ def name_segments
77
+ @name_segments ||= @args[0].to_s.split("/")
78
+ end
79
+
80
+ def qualified_class_name
81
+ @qualified_class_name ||= name_segments.map { |s| camelize(s) }.join("::")
82
+ end
83
+
84
+ def flat_class_name
85
+ @flat_class_name ||= name_segments.map { |s| camelize(s) }.join
86
+ end
87
+
88
+ def table_name
89
+ @table_name ||= begin
90
+ segs = name_segments.map { |s| underscore(camelize(s)) }
91
+ segs[-1] = pluralize(segs[-1])
92
+ segs.join("_")
93
+ end
94
+ end
95
+
96
+ def model_relative_path
97
+ @model_relative_path ||= if name_segments.length > 1
98
+ File.join(*name_segments[0..-2], model_file_basename)
99
+ else
100
+ model_file_basename
101
+ end
102
+ end
103
+
104
+ def model_file_basename
105
+ @model_file_basename ||= underscore(name_segments.last)
106
+ end
107
+
108
+ def model_name
109
+ @model_name ||= @args[0].nil? ? nil : qualified_class_name
110
+ end
111
+
112
+ def field_args
113
+ @field_args ||= @args[1..] || []
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -10,6 +10,7 @@ class Roda
10
10
  exit 1
11
11
  end
12
12
 
13
+ puts "* creating routes"
13
14
  generate_routes
14
15
  generate_views
15
16
  generate_tests
@@ -19,7 +20,7 @@ class Roda
19
20
  private
20
21
 
21
22
  def generate_routes
22
- filename = File.join(ensure_and_get_path("app/routes"), "#{branch_name}.rb")
23
+ filename = File.join(ensure_and_get_path("app/routes", branch_name), "#{branch_name}.rb")
23
24
  route_definitions = routes_list.map do |route_str|
24
25
  method, name = parse_route_string(route_str)
25
26
  view_line = (method == "get" && must_generate_views?) ? "\n view('#{name}')" : ""
@@ -44,7 +45,7 @@ class Roda
44
45
  end
45
46
  RUBY
46
47
  File.write(filename, content)
47
- puts "* created routes file: #{filename}"
48
+ puts_create_message(filename)
48
49
  end
49
50
 
50
51
  def generate_views
@@ -56,14 +57,14 @@ class Roda
56
57
  if method == "get"
57
58
  view_filename = File.join(branch_views_dir, "#{name}.erb")
58
59
  File.write(view_filename, "")
59
- puts "* created view file: #{view_filename}"
60
+ puts_create_message(view_filename)
60
61
  end
61
62
  end
62
63
  end
63
64
  end
64
65
 
65
66
  def generate_tests
66
- test_filename = File.join(ensure_and_get_path("spec/app/routes"), "#{branch_name}_spec.rb")
67
+ test_filename = File.join(ensure_and_get_path("spec/app/routes", branch_name), "#{branch_name}_spec.rb")
67
68
  nesting_level = branch_name.count("/")
68
69
  relative_spec_helper_path = "../" * (2 + nesting_level) + "spec_helper"
69
70
 
@@ -83,7 +84,7 @@ class Roda
83
84
  end
84
85
  RUBY
85
86
  File.write(test_filename, test_content)
86
- puts "* created test file: #{test_filename}"
87
+ puts_create_message(test_filename)
87
88
  end
88
89
 
89
90
  def must_generate_views?
@@ -96,13 +97,6 @@ class Roda
96
97
  [method, name]
97
98
  end
98
99
 
99
- def ensure_and_get_path(path)
100
- full_path_dir = File.join(path, File.dirname(branch_name))
101
- FileUtils.mkdir_p(full_path_dir) unless File.directory?(full_path_dir)
102
-
103
- path
104
- end
105
-
106
100
  def routes_list
107
101
  @routes_list ||= @args[1..]
108
102
  end
@@ -15,6 +15,11 @@ class Roda
15
15
  Routes.new(context:, args:, options:).call
16
16
  end
17
17
 
18
+ desc "model", "Create model and migrations"
19
+ def model(*args)
20
+ Model.new(context:, args:, options:).call
21
+ end
22
+
18
23
  private
19
24
 
20
25
  def context
@@ -97,8 +97,11 @@ class Roda
97
97
  erb_cp_file("front-end", "esbuild.js")
98
98
  erb_cp_file("front-end", "package.json")
99
99
  cp_dir("front-end", "app/views")
100
+ puts_create_message("app/views")
100
101
  cp_dir("front-end", "public/assets")
102
+ puts_create_message("public/assets")
101
103
  cp_dir("front-end", "public/images")
104
+ puts_create_message("public/images")
102
105
  end
103
106
  end
104
107
 
@@ -118,6 +121,7 @@ class Roda
118
121
  erb_cp_file("rodauth", "db/migrations/001_add_rodauth.rb")
119
122
  if @context.fullstack?
120
123
  cp_file("rodauth", "app/views/create-account.erb")
124
+ puts_create_message("app/views/create-account.erb")
121
125
  end
122
126
  end
123
127
  end
@@ -4,16 +4,25 @@ class Roda
4
4
  include Helpers::Template
5
5
  include Helpers::Ids
6
6
 
7
- def initialize(context: MainContext.new, args: [], options: {}, dir: nil)
7
+ attr_reader :pastel
8
+
9
+ def initialize(context: MainContext.new, args: [], options: {}, dir: nil, pastel: Pastel.new)
8
10
  @context = context
9
11
  @args = args
10
12
  @options = options
11
13
  @dir = dir
14
+ @pastel = pastel
12
15
  end
13
16
 
14
17
  def call
15
18
  false
16
19
  end
20
+
21
+ protected
22
+
23
+ def puts_create_message(path)
24
+ puts(pastel.green(" create ") + path)
25
+ end
17
26
  end
18
27
  end
19
28
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Roda
4
+ module Project
5
+ module Helpers
6
+ module Inflections
7
+ module_function
8
+
9
+ def camelize(str)
10
+ return str if /[A-Z]/.match?(str)
11
+
12
+ str.split("_").map(&:capitalize).join
13
+ end
14
+
15
+ def underscore(str)
16
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
17
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
18
+ .tr("-", "_")
19
+ .downcase
20
+ end
21
+
22
+ def pluralize(str)
23
+ return str if str.end_with?("s")
24
+
25
+ if str.end_with?("y") && !str.end_with?("ay", "ey", "oy", "uy")
26
+ "#{str[0..-2]}ies"
27
+ elsif str.end_with?("s", "x", "z", "ch", "sh")
28
+ "#{str}es"
29
+ else
30
+ "#{str}s"
31
+ end
32
+ end
33
+
34
+ def singularize(str)
35
+ if str.end_with?("ies")
36
+ "#{str[0..-4]}y"
37
+ elsif str.end_with?("es") && !str.end_with?("ques")
38
+ str[0..-3]
39
+ elsif str.end_with?("s") && !str.end_with?("ss")
40
+ str[0..-2]
41
+ else
42
+ str
43
+ end
44
+ end
45
+
46
+ def singular?(str)
47
+ str == singularize(str)
48
+ end
49
+
50
+ def plural?(str)
51
+ str == pluralize(str)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -22,10 +22,6 @@ class Roda
22
22
 
23
23
  yield
24
24
  end
25
-
26
- def pastel
27
- @pastel ||= Pastel.new
28
- end
29
25
  end
30
26
  end
31
27
  end
@@ -34,6 +34,13 @@ class Roda
34
34
  )
35
35
  end
36
36
 
37
+ def ensure_and_get_path(path, name)
38
+ full_path_dir = File.join(path, File.dirname(name))
39
+ FileUtils.mkdir_p(full_path_dir) unless File.directory?(full_path_dir)
40
+
41
+ path
42
+ end
43
+
37
44
  def templates_root
38
45
  @templates_root ||= "../templates"
39
46
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Roda
4
4
  module Project
5
- VERSION = "0.1.9"
5
+ VERSION = "0.1.11"
6
6
  end
7
7
  end
data/lib/roda/project.rb CHANGED
@@ -55,14 +55,22 @@ require "thor"
55
55
  require_relative "project/helpers/ids"
56
56
  require_relative "project/helpers/interactive_input"
57
57
  require_relative "project/helpers/template"
58
+ require_relative "project/helpers/inflections"
58
59
  # Base
59
60
  require_relative "project/main_context"
60
61
  require_relative "project/generator"
61
62
  require_relative "project/cli"
62
- # Generators
63
63
  require_relative "project/bin/generator"
64
+ # Generators/migration
64
65
  require_relative "project/bin/generators/migration"
66
+ require_relative "project/bin/generators/migration/action_detector"
67
+ require_relative "project/bin/generators/migration/field_parser"
68
+ require_relative "project/bin/generators/migration/code_builder"
69
+ # Generators/model
70
+ require_relative "project/bin/generators/model"
71
+ # Generators/routes
65
72
  require_relative "project/bin/generators/routes"
73
+ # Generators/base
66
74
  require_relative "project/bin/generators"
67
75
  # Version
68
76
  require_relative "project/version"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda-project
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henrique F. Teixeira
@@ -80,10 +80,15 @@ files:
80
80
  - lib/roda/project/bin/generator.rb
81
81
  - lib/roda/project/bin/generators.rb
82
82
  - lib/roda/project/bin/generators/migration.rb
83
+ - lib/roda/project/bin/generators/migration/action_detector.rb
84
+ - lib/roda/project/bin/generators/migration/code_builder.rb
85
+ - lib/roda/project/bin/generators/migration/field_parser.rb
86
+ - lib/roda/project/bin/generators/model.rb
83
87
  - lib/roda/project/bin/generators/routes.rb
84
88
  - lib/roda/project/cli.rb
85
89
  - lib/roda/project/generator.rb
86
90
  - lib/roda/project/helpers/ids.rb
91
+ - lib/roda/project/helpers/inflections.rb
87
92
  - lib/roda/project/helpers/interactive_input.rb
88
93
  - lib/roda/project/helpers/template.rb
89
94
  - lib/roda/project/main_context.rb
@@ -159,7 +164,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
164
  requirements:
160
165
  - - ">="
161
166
  - !ruby/object:Gem::Version
162
- version: 3.2.0
167
+ version: 3.0.0
163
168
  required_rubygems_version: !ruby/object:Gem::Requirement
164
169
  requirements:
165
170
  - - ">="