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 +4 -4
- data/lib/roda/project/bin/generators/migration/action_detector.rb +64 -0
- data/lib/roda/project/bin/generators/migration/code_builder.rb +186 -0
- data/lib/roda/project/bin/generators/migration/field_parser.rb +129 -0
- data/lib/roda/project/bin/generators/migration.rb +23 -15
- data/lib/roda/project/bin/generators/model.rb +119 -0
- data/lib/roda/project/bin/generators/routes.rb +6 -12
- data/lib/roda/project/bin/generators.rb +5 -0
- data/lib/roda/project/cli.rb +4 -0
- data/lib/roda/project/generator.rb +10 -1
- data/lib/roda/project/helpers/inflections.rb +56 -0
- data/lib/roda/project/helpers/interactive_input.rb +0 -4
- data/lib/roda/project/helpers/template.rb +7 -0
- data/lib/roda/project/version.rb +1 -1
- data/lib/roda/project.rb +9 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3fceab0441f37da52ca87b8c028d4fa72573854e2a1adc25f34f76ac6e7b4e2a
|
|
4
|
+
data.tar.gz: 24c9c54923283bee8394d0597206feed0d1a914cbf30caa1368bef696a2ffa3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/roda/project/cli.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
data/lib/roda/project/version.rb
CHANGED
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.
|
|
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.
|
|
167
|
+
version: 3.0.0
|
|
163
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
169
|
requirements:
|
|
165
170
|
- - ">="
|