brut 0.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/CODE_OF_CONDUCT.txt +99 -0
- data/Dockerfile.dx +32 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +370 -0
- data/README.md +21 -0
- data/Rakefile +1 -0
- data/bin/bin_kit.rb +39 -0
- data/bin/rake +27 -0
- data/bin/setup +145 -0
- data/brut.gemspec +60 -0
- data/docker-compose.dx.yml +16 -0
- data/dx/build +26 -0
- data/dx/docker-compose.env +22 -0
- data/dx/dx.sh.lib +24 -0
- data/dx/exec +58 -0
- data/dx/prune +19 -0
- data/dx/setupkit.sh.lib +144 -0
- data/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/dx/start +30 -0
- data/dx/stop +23 -0
- data/lib/brut/back_end/action.rb +3 -0
- data/lib/brut/back_end/result.rb +46 -0
- data/lib/brut/back_end/seed_data.rb +24 -0
- data/lib/brut/back_end/validator.rb +3 -0
- data/lib/brut/back_end/validators/form_validator.rb +37 -0
- data/lib/brut/cli/app.rb +130 -0
- data/lib/brut/cli/app_runner.rb +219 -0
- data/lib/brut/cli/apps/build_assets.rb +123 -0
- data/lib/brut/cli/apps/db.rb +279 -0
- data/lib/brut/cli/apps/scaffold.rb +256 -0
- data/lib/brut/cli/apps/test.rb +200 -0
- data/lib/brut/cli/command.rb +130 -0
- data/lib/brut/cli/error.rb +12 -0
- data/lib/brut/cli/execution_results.rb +81 -0
- data/lib/brut/cli/executor.rb +37 -0
- data/lib/brut/cli/options.rb +46 -0
- data/lib/brut/cli/output.rb +30 -0
- data/lib/brut/cli.rb +24 -0
- data/lib/brut/factory_bot.rb +20 -0
- data/lib/brut/framework/app.rb +55 -0
- data/lib/brut/framework/config.rb +415 -0
- data/lib/brut/framework/container.rb +190 -0
- data/lib/brut/framework/errors/abstract_method.rb +9 -0
- data/lib/brut/framework/errors/bug.rb +14 -0
- data/lib/brut/framework/errors/not_found.rb +10 -0
- data/lib/brut/framework/errors.rb +14 -0
- data/lib/brut/framework/fussy_type_enforcement.rb +50 -0
- data/lib/brut/framework/mcp.rb +215 -0
- data/lib/brut/framework/project_environment.rb +18 -0
- data/lib/brut/framework.rb +13 -0
- data/lib/brut/front_end/asset_metadata.rb +76 -0
- data/lib/brut/front_end/component.rb +213 -0
- data/lib/brut/front_end/components/form_tag.rb +71 -0
- data/lib/brut/front_end/components/i18n_translations.rb +36 -0
- data/lib/brut/front_end/components/input.rb +13 -0
- data/lib/brut/front_end/components/inputs/csrf_token.rb +8 -0
- data/lib/brut/front_end/components/inputs/select.rb +100 -0
- data/lib/brut/front_end/components/inputs/text_field.rb +63 -0
- data/lib/brut/front_end/components/inputs/textarea.rb +51 -0
- data/lib/brut/front_end/components/locale_detection.rb +25 -0
- data/lib/brut/front_end/components/page_identifier.rb +13 -0
- data/lib/brut/front_end/components/timestamp.rb +33 -0
- data/lib/brut/front_end/download.rb +23 -0
- data/lib/brut/front_end/flash.rb +57 -0
- data/lib/brut/front_end/form.rb +171 -0
- data/lib/brut/front_end/forms/constraint_violation.rb +39 -0
- data/lib/brut/front_end/forms/input.rb +119 -0
- data/lib/brut/front_end/forms/input_definition.rb +100 -0
- data/lib/brut/front_end/forms/validity_state.rb +36 -0
- data/lib/brut/front_end/handler.rb +48 -0
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +11 -0
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +22 -0
- data/lib/brut/front_end/handling_results.rb +14 -0
- data/lib/brut/front_end/http_method.rb +33 -0
- data/lib/brut/front_end/http_status.rb +16 -0
- data/lib/brut/front_end/middleware.rb +7 -0
- data/lib/brut/front_end/middlewares/reload_app.rb +31 -0
- data/lib/brut/front_end/page.rb +47 -0
- data/lib/brut/front_end/request_context.rb +82 -0
- data/lib/brut/front_end/route_hook.rb +15 -0
- data/lib/brut/front_end/route_hooks/age_flash.rb +8 -0
- data/lib/brut/front_end/route_hooks/csp_no_inline_scripts.rb +17 -0
- data/lib/brut/front_end/route_hooks/csp_no_inline_styles_or_scripts.rb +46 -0
- data/lib/brut/front_end/route_hooks/locale_detection.rb +24 -0
- data/lib/brut/front_end/route_hooks/setup_request_context.rb +11 -0
- data/lib/brut/front_end/routing.rb +236 -0
- data/lib/brut/front_end/session.rb +56 -0
- data/lib/brut/front_end/template.rb +32 -0
- data/lib/brut/front_end/templates/block_filter.rb +60 -0
- data/lib/brut/front_end/templates/erb_engine.rb +26 -0
- data/lib/brut/front_end/templates/erb_parser.rb +84 -0
- data/lib/brut/front_end/templates/escapable_filter.rb +18 -0
- data/lib/brut/front_end/templates/html_safe_string.rb +40 -0
- data/lib/brut/i18n/base_methods.rb +168 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +4 -0
- data/lib/brut/i18n/http_accept_language.rb +68 -0
- data/lib/brut/i18n.rb +6 -0
- data/lib/brut/instrumentation/basic.rb +66 -0
- data/lib/brut/instrumentation/event.rb +19 -0
- data/lib/brut/instrumentation/http_event.rb +5 -0
- data/lib/brut/instrumentation/subscriber.rb +41 -0
- data/lib/brut/instrumentation.rb +11 -0
- data/lib/brut/junk_drawer.rb +88 -0
- data/lib/brut/sinatra_helpers.rb +183 -0
- data/lib/brut/spec_support/component_support.rb +49 -0
- data/lib/brut/spec_support/flash_support.rb +7 -0
- data/lib/brut/spec_support/general_support.rb +18 -0
- data/lib/brut/spec_support/handler_support.rb +7 -0
- data/lib/brut/spec_support/matcher.rb +9 -0
- data/lib/brut/spec_support/matchers/be_a_bug.rb +14 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +14 -0
- data/lib/brut/spec_support/matchers/be_routing_for.rb +11 -0
- data/lib/brut/spec_support/matchers/have_constraint_violation.rb +56 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +69 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +20 -0
- data/lib/brut/spec_support/matchers/have_returned_http_status.rb +27 -0
- data/lib/brut/spec_support/session_support.rb +3 -0
- data/lib/brut/spec_support.rb +12 -0
- data/lib/brut/version.rb +3 -0
- data/lib/brut.rb +38 -0
- data/lib/sequel/extensions/brut_instrumentation.rb +37 -0
- data/lib/sequel/extensions/brut_migrations.rb +98 -0
- data/lib/sequel/plugins/created_at.rb +14 -0
- data/lib/sequel/plugins/external_id.rb +45 -0
- data/lib/sequel/plugins/find_bang.rb +13 -0
- data/lib/sequel/plugins.rb +3 -0
- metadata +484 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require "sequel"
|
|
2
|
+
require "uri"
|
|
3
|
+
require "date"
|
|
4
|
+
require "brut/cli"
|
|
5
|
+
|
|
6
|
+
class Brut::CLI::Apps::DB < Brut::CLI::App
|
|
7
|
+
description "Manage your database in development, test, and production"
|
|
8
|
+
|
|
9
|
+
class Seed < Brut::CLI::Command
|
|
10
|
+
description "Load seed data into the database"
|
|
11
|
+
requires_project_env default: "development"
|
|
12
|
+
|
|
13
|
+
def handle_bootstrap_exception(ex)
|
|
14
|
+
case ex
|
|
15
|
+
when Sequel::DatabaseConnectionError
|
|
16
|
+
err.puts "Database needs to be created"
|
|
17
|
+
stop_execution
|
|
18
|
+
when Sequel::DatabaseError
|
|
19
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
20
|
+
err.puts "Migrations need to be run"
|
|
21
|
+
stop_execution
|
|
22
|
+
else
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def execute
|
|
31
|
+
seeds_dir = Brut.container.db_seeds_dir
|
|
32
|
+
Dir["#{seeds_dir}/*.rb"].each do |file|
|
|
33
|
+
require file
|
|
34
|
+
end
|
|
35
|
+
seed_data = Brut::Backend::SeedData.new
|
|
36
|
+
seed_data.setup!
|
|
37
|
+
seed_data.load_seeds!
|
|
38
|
+
0
|
|
39
|
+
rescue Sequel::UniqueConstraintViolation => ex
|
|
40
|
+
out.puts "Seed data may have already been loaded: #{ex}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Rebuild < Brut::CLI::Command
|
|
45
|
+
description "Drop, re-create, and run migrations, effecitvely rebuilding the entire database"
|
|
46
|
+
opts.on("--[no-]seeds","Load seed data after applying migrations")
|
|
47
|
+
|
|
48
|
+
requires_project_env default: "development"
|
|
49
|
+
|
|
50
|
+
def handle_bootstrap_exception(ex)
|
|
51
|
+
case ex
|
|
52
|
+
when Sequel::DatabaseConnectionError
|
|
53
|
+
continue_execution
|
|
54
|
+
when Sequel::DatabaseError
|
|
55
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
56
|
+
continue_execution
|
|
57
|
+
else
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def execute
|
|
66
|
+
result = delegate_to_commands(Drop, Create, Migrate)
|
|
67
|
+
if result.ok? && options.seeds?
|
|
68
|
+
result = delegate_to_command(Seed)
|
|
69
|
+
end
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Create < Brut::CLI::Command
|
|
75
|
+
description "Create the database if it does not exist"
|
|
76
|
+
requires_project_env default: "development"
|
|
77
|
+
|
|
78
|
+
def handle_bootstrap_exception(ex)
|
|
79
|
+
case ex
|
|
80
|
+
when Sequel::DatabaseConnectionError
|
|
81
|
+
uri_no_database = URI(Brut.container.database_url.to_s)
|
|
82
|
+
database_name = uri_no_database.path.gsub(/^\//,"")
|
|
83
|
+
uri_no_database.path = ""
|
|
84
|
+
begin
|
|
85
|
+
connection = Sequel.connect(uri_no_database.to_s)
|
|
86
|
+
out.puts "#{database_name} does not exit. Creating..."
|
|
87
|
+
connection.run("CREATE DATABASE \"#{database_name}\"")
|
|
88
|
+
connection.disconnect
|
|
89
|
+
rescue => ex
|
|
90
|
+
err.puts ex.message
|
|
91
|
+
end
|
|
92
|
+
stop_execution
|
|
93
|
+
when Sequel::DatabaseError
|
|
94
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
95
|
+
out.puts "Migrations need to be run"
|
|
96
|
+
continue_execution
|
|
97
|
+
else
|
|
98
|
+
super
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
super
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
def execute
|
|
105
|
+
connection = Sequel.connect(Brut.container.database_url)
|
|
106
|
+
out.puts "Database already exists"
|
|
107
|
+
connection.disconnect
|
|
108
|
+
0
|
|
109
|
+
rescue => ex
|
|
110
|
+
handle_bootstrap_exception(ex)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class Drop < Brut::CLI::Command
|
|
115
|
+
description "Drop the database if it exists"
|
|
116
|
+
requires_project_env default: "development"
|
|
117
|
+
|
|
118
|
+
def handle_bootstrap_exception(ex)
|
|
119
|
+
case ex
|
|
120
|
+
when Sequel::DatabaseConnectionError
|
|
121
|
+
out.puts "Database does not exist"
|
|
122
|
+
stop_execution
|
|
123
|
+
when Sequel::DatabaseError
|
|
124
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
125
|
+
continue_execution
|
|
126
|
+
else
|
|
127
|
+
super
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def execute
|
|
135
|
+
uri_no_database = URI(Brut.container.database_url.to_s)
|
|
136
|
+
database_name = uri_no_database.path.gsub(/^\//,"")
|
|
137
|
+
uri_no_database.path = ""
|
|
138
|
+
out.puts "Database exists. Dropping..."
|
|
139
|
+
begin
|
|
140
|
+
Brut.container.sequel_db_handle.disconnect
|
|
141
|
+
rescue Sequel::DatabaseConnectionError
|
|
142
|
+
end
|
|
143
|
+
connection = Sequel.connect(uri_no_database.to_s)
|
|
144
|
+
connection.run("DROP DATABASE IF EXISTS \"#{database_name}\"")
|
|
145
|
+
connection.disconnect
|
|
146
|
+
0
|
|
147
|
+
rescue => ex
|
|
148
|
+
handle_bootstrap_exception(ex)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class Migrate < Brut::CLI::Command
|
|
153
|
+
description "Apply any outstanding migrations to the database"
|
|
154
|
+
requires_project_env default: "development"
|
|
155
|
+
|
|
156
|
+
def handle_bootstrap_exception(ex)
|
|
157
|
+
case ex
|
|
158
|
+
when Sequel::DatabaseConnectionError
|
|
159
|
+
err.puts "Database does not exist. Create it first"
|
|
160
|
+
stop_execution
|
|
161
|
+
when Sequel::DatabaseError
|
|
162
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
163
|
+
# ignoring - we are running migrations which will address this
|
|
164
|
+
continue_execution
|
|
165
|
+
else
|
|
166
|
+
super
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
super
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def execute
|
|
174
|
+
Sequel.extension :migration
|
|
175
|
+
Brut.container.sequel_db_handle.extension :brut_migrations
|
|
176
|
+
migrations_dir = Brut.container.migrations_dir
|
|
177
|
+
if !migrations_dir.exist?
|
|
178
|
+
err.puts "#{migrations_dir} doesn't exist"
|
|
179
|
+
return
|
|
180
|
+
end
|
|
181
|
+
Brut.container.sequel_db_handle.extension :pg_array
|
|
182
|
+
|
|
183
|
+
logger = Logger.new(STDOUT)
|
|
184
|
+
logger.level = ENV["LOG_LEVEL"]
|
|
185
|
+
indent = ""
|
|
186
|
+
logger.formatter = proc { |severity,time,progname,message|
|
|
187
|
+
formatted = "#{indent} - #{message}\n"
|
|
188
|
+
if message =~ /^Begin applying/
|
|
189
|
+
indent = " "
|
|
190
|
+
elsif message =~ /^Finished applying/
|
|
191
|
+
indent = ""
|
|
192
|
+
formatted = "#{indent} - #{message}\n"
|
|
193
|
+
end
|
|
194
|
+
formatted
|
|
195
|
+
}
|
|
196
|
+
Brut.container.sequel_db_handle.logger = logger
|
|
197
|
+
Sequel::Migrator.run(Brut.container.sequel_db_handle,migrations_dir)
|
|
198
|
+
out.puts "Migrations applied"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class NewMigration < Brut::CLI::Command
|
|
203
|
+
description "Create a new migration file"
|
|
204
|
+
args "migration_name"
|
|
205
|
+
|
|
206
|
+
def before_execute
|
|
207
|
+
ENV["RACK_ENV"] = "development"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def execute
|
|
211
|
+
if @args.length == 0
|
|
212
|
+
return abort_execution("You must provide a name for the migration")
|
|
213
|
+
end
|
|
214
|
+
migrations_dir = Brut.container.migrations_dir
|
|
215
|
+
name = @args.join(" ").gsub(/[^\w\d\-]/,"-")
|
|
216
|
+
date = DateTime.now.strftime("%Y%m%d%H%M%S")
|
|
217
|
+
file_name = migrations_dir / "#{date}_#{name}.rb"
|
|
218
|
+
File.open(file_name,"w") do |file|
|
|
219
|
+
file.puts "Sequel.migration do"
|
|
220
|
+
file.puts " up do"
|
|
221
|
+
file.puts " end"
|
|
222
|
+
file.puts "end"
|
|
223
|
+
end
|
|
224
|
+
relative_path = file_name.relative_path_from(Brut.container.project_root)
|
|
225
|
+
out.puts "Migration created:\n #{relative_path}"
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class Status < Brut::CLI::Command
|
|
230
|
+
description "Check the status of the database and migrations"
|
|
231
|
+
requires_project_env default: "development"
|
|
232
|
+
|
|
233
|
+
def handle_bootstrap_exception(ex)
|
|
234
|
+
case ex
|
|
235
|
+
when Sequel::DatabaseConnectionError
|
|
236
|
+
uri_no_database = URI(Brut.container.database_url.to_s)
|
|
237
|
+
database_name = uri_no_database.path.gsub(/^\//,"")
|
|
238
|
+
uri_no_database.path = ""
|
|
239
|
+
begin
|
|
240
|
+
connection = Sequel.connect(uri_no_database.to_s)
|
|
241
|
+
out.puts "Database Server is Up"
|
|
242
|
+
out.puts "Database #{database_name} does not exist"
|
|
243
|
+
rescue => ex
|
|
244
|
+
err.puts ex.message
|
|
245
|
+
end
|
|
246
|
+
stop_execution
|
|
247
|
+
when Sequel::DatabaseError
|
|
248
|
+
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
249
|
+
err.puts "Migrations need to be run"
|
|
250
|
+
continue_execution
|
|
251
|
+
else
|
|
252
|
+
super
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def execute
|
|
258
|
+
database_name = URI(Brut.container.database_url).path
|
|
259
|
+
connection = Brut.container.sequel_db_handle
|
|
260
|
+
out.puts "Database Server is Up"
|
|
261
|
+
out.puts "Database #{database_name} exists"
|
|
262
|
+
migrations_run = if connection.table_exists?("schema_migrations")
|
|
263
|
+
connection["select filename from schema_migrations order by filename"].all.map { |_| _[:filename] }
|
|
264
|
+
else
|
|
265
|
+
[]
|
|
266
|
+
end
|
|
267
|
+
migration_files = Dir[Brut.container.migrations_dir / "*.rb"].map { |file|
|
|
268
|
+
filename = Pathname(file).basename.to_s
|
|
269
|
+
}
|
|
270
|
+
max_length = migration_files.map(&:length).max
|
|
271
|
+
printf_string = "%-#{max_length}s - %s\n"
|
|
272
|
+
migration_files.each do |filename|
|
|
273
|
+
applied = migrations_run.include?(filename)
|
|
274
|
+
printf(printf_string,filename,applied ? "✅ APPLIED" : "❌ NOT APPLIED")
|
|
275
|
+
end
|
|
276
|
+
0
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
require "brut/cli"
|
|
2
|
+
|
|
3
|
+
class Brut::CLI::Apps::Scaffold < Brut::CLI::App
|
|
4
|
+
description "Create scaffolds of various files to help develop more quckly"
|
|
5
|
+
opts.on("--overwrite", "If set, any files that exists already will be overwritten by new scaffolds")
|
|
6
|
+
opts.on("--dry-run", "If set, no files are changed. You will see output of what would happen without this flag")
|
|
7
|
+
|
|
8
|
+
def before_execute
|
|
9
|
+
ENV["RACK_ENV"] = "development"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Test < Brut::CLI::Command
|
|
13
|
+
description "Create a test for a given file in the app"
|
|
14
|
+
args "source_file_paths..."
|
|
15
|
+
def execute
|
|
16
|
+
if args.empty?
|
|
17
|
+
err.puts "'test' requires one or more files to scaffold a test for"
|
|
18
|
+
return 1
|
|
19
|
+
end
|
|
20
|
+
files_to_test_files = args.map { |arg|
|
|
21
|
+
Pathname(arg).expand_path
|
|
22
|
+
}.map { |pathname|
|
|
23
|
+
relative = pathname.relative_path_from(Brut.container.app_src_dir)
|
|
24
|
+
test_file = Brut.container.app_specs_dir / relative.dirname / "#{relative.basename(relative.extname)}.spec.rb"
|
|
25
|
+
[ pathname, test_file ]
|
|
26
|
+
}.to_h
|
|
27
|
+
|
|
28
|
+
non_existent_sources = files_to_test_files.keys.select { |pathname| !pathname.exist? }
|
|
29
|
+
existent_destinations = files_to_test_files.values.select { |pathname| pathname.exist? }
|
|
30
|
+
|
|
31
|
+
if non_existent_sources.any?
|
|
32
|
+
relative_paths = non_existent_sources.map { |pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
33
|
+
err.puts "Not all input files exist:"
|
|
34
|
+
relative_paths.each do |file|
|
|
35
|
+
err.puts file
|
|
36
|
+
end
|
|
37
|
+
return 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if existent_destinations.any? && !global_options.overwrite?
|
|
41
|
+
relative_paths = existent_destinations.map { |pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
42
|
+
err.puts "Some files to be generated already exist. Set --overwrite to overwrite them:"
|
|
43
|
+
relative_paths.each do |file|
|
|
44
|
+
err.puts file
|
|
45
|
+
end
|
|
46
|
+
return 1
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
files_to_test_files.each do |source,destination|
|
|
50
|
+
result = Prism.parse_file(source.to_s)
|
|
51
|
+
if !result
|
|
52
|
+
raise "For some reason Prism did not parse #{source.to_s}"
|
|
53
|
+
end
|
|
54
|
+
classes = find_classes(result.value).map { |(module_nodes,class_node)|
|
|
55
|
+
(module_nodes.map(&:constant_path).map(&:full_name).map(&:to_s) + [class_node.constant_path.full_name.to_s]).compact.join("::")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
out.puts "#{destination} will contain tests for:\n#{classes.join("\n")}\n\n"
|
|
60
|
+
|
|
61
|
+
code = ["require \"spec_helper\"\n"] + classes.map { |class_name|
|
|
62
|
+
%{RSpec.describe #{class_name} do
|
|
63
|
+
it "should have tests" do
|
|
64
|
+
expect(false).to eq(true)
|
|
65
|
+
end
|
|
66
|
+
end}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if global_options.dry_run?
|
|
70
|
+
puts code
|
|
71
|
+
else
|
|
72
|
+
FileUtils.mkdir_p destination.dirname
|
|
73
|
+
File.open(destination,"w") do |file|
|
|
74
|
+
file.puts code
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def find_classes(ast,current_modules = [])
|
|
85
|
+
classes = []
|
|
86
|
+
if ast.nil?
|
|
87
|
+
return classes
|
|
88
|
+
end
|
|
89
|
+
new_module = nil
|
|
90
|
+
if ast.kind_of?(Prism::ClassNode)
|
|
91
|
+
classes << [ current_modules, ast ]
|
|
92
|
+
new_module = ast
|
|
93
|
+
elsif ast.kind_of?(Prism::ModuleNode)
|
|
94
|
+
new_module = ast
|
|
95
|
+
end
|
|
96
|
+
ast.child_nodes.each do |child|
|
|
97
|
+
new_current_modules = current_modules + [ new_module ]
|
|
98
|
+
result = find_classes(child, new_current_modules.compact)
|
|
99
|
+
classes = classes + result
|
|
100
|
+
end
|
|
101
|
+
classes
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
class Component < Brut::CLI::Command
|
|
106
|
+
description "Create a new component, template, and associated test"
|
|
107
|
+
opts.on("--page","If set, this component is for a specific page and won't go with the other components")
|
|
108
|
+
args "ComponentName"
|
|
109
|
+
def execute
|
|
110
|
+
if args.length != 1
|
|
111
|
+
raise "component requires exactly one argument, got #{args.length}"
|
|
112
|
+
end
|
|
113
|
+
class_name = RichString.new(args[0])
|
|
114
|
+
if class_name.to_s !~ /Component$/
|
|
115
|
+
class_name = RichString.new(class_name.to_s + "Component")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
relative_path = class_name.underscorized
|
|
119
|
+
|
|
120
|
+
components_src_dir = Brut.container.components_src_dir
|
|
121
|
+
components_specs_dir = Brut.container.components_specs_dir
|
|
122
|
+
|
|
123
|
+
if options.page?
|
|
124
|
+
components_src_dir = Brut.container.pages_src_dir
|
|
125
|
+
components_specs_dir = Brut.container.pages_specs_dir
|
|
126
|
+
if class_name.to_s !~ /::/
|
|
127
|
+
raise "component #{class_name} cannot be a page component - it must be an inner class of an existing page"
|
|
128
|
+
else
|
|
129
|
+
existing_page = RichString.new(class_name.to_s.split(/::/)[0..-2].join("::")).underscorized.to_s + ".rb"
|
|
130
|
+
|
|
131
|
+
if !(components_src_dir / existing_page).exist?
|
|
132
|
+
raise "#{class_name} was set as a page component, however we cannot find the page it belongs in. File #{existing_page} does not exist and should contain that page"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
source_path = Pathname( (components_src_dir / relative_path).to_s + ".rb" )
|
|
138
|
+
html_source_path = Pathname( (components_src_dir / relative_path).to_s + ".html.erb" )
|
|
139
|
+
spec_path = Pathname( (components_specs_dir / relative_path).to_s + ".spec.rb" )
|
|
140
|
+
|
|
141
|
+
exists = [
|
|
142
|
+
source_path,
|
|
143
|
+
html_source_path,
|
|
144
|
+
spec_path,
|
|
145
|
+
].select(&:exist?)
|
|
146
|
+
|
|
147
|
+
if exists.any? && !global_options.overwrite?
|
|
148
|
+
exists.each do |path|
|
|
149
|
+
err.puts "'#{path.relative_path_from(Brut.container.project_root)}' exists already"
|
|
150
|
+
end
|
|
151
|
+
err.puts "Re-run with --overwrite to overwrite these files"
|
|
152
|
+
return 1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if global_options.dry_run?
|
|
156
|
+
puts "FileUtils.mkdir_p #{source_path.dirname}"
|
|
157
|
+
puts "FileUtils.mkdir_p #{html_source_path.dirname}"
|
|
158
|
+
puts "FileUtils.mkdir_p #{spec_path.dirname}"
|
|
159
|
+
else
|
|
160
|
+
FileUtils.mkdir_p source_path.dirname
|
|
161
|
+
FileUtils.mkdir_p html_source_path.dirname
|
|
162
|
+
FileUtils.mkdir_p spec_path.dirname
|
|
163
|
+
|
|
164
|
+
File.open(source_path,"w") do |file|
|
|
165
|
+
file.puts %{class #{class_name} < AppComponent
|
|
166
|
+
def initialize
|
|
167
|
+
end
|
|
168
|
+
end}
|
|
169
|
+
end
|
|
170
|
+
File.open(html_source_path,"w") do |file|
|
|
171
|
+
file.puts "<h1>#{class_name} is ready!</h1>"
|
|
172
|
+
end
|
|
173
|
+
File.open(spec_path,"w") do |file|
|
|
174
|
+
file.puts %{require "spec_helper"
|
|
175
|
+
|
|
176
|
+
RSpec.describe #{class_name} do
|
|
177
|
+
it "should have tests" do
|
|
178
|
+
expect(true).to eq(false)
|
|
179
|
+
end
|
|
180
|
+
end}
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
out.puts "Component source is in #{source_path.relative_path_from(Brut.container.project_root)}"
|
|
184
|
+
out.puts "Component HTML template is in #{html_source_path.relative_path_from(Brut.container.project_root)}"
|
|
185
|
+
out.puts "Component test is in #{spec_path.relative_path_from(Brut.container.project_root)}"
|
|
186
|
+
0
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
class CustomElementTest < Brut::CLI::Command
|
|
190
|
+
description "Create a test for a custom element in your app"
|
|
191
|
+
args "path_to_js_files..."
|
|
192
|
+
def execute
|
|
193
|
+
if args.empty?
|
|
194
|
+
err.puts "'custom-element-test' requires one or more files to scaffold a test for"
|
|
195
|
+
return 1
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
if args.any? { |file| Pathname(file).extname != ".js" }
|
|
199
|
+
err.puts "'custom-element-test' must be given only .js files"
|
|
200
|
+
return 1
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
files_to_create = args.map { |arg|
|
|
204
|
+
path = Pathname(arg).expand_path
|
|
205
|
+
relative_path = path.relative_path_from(Brut.container.js_src_dir)
|
|
206
|
+
relative_path_as_spec = relative_path.dirname / (relative_path.basename(relative_path.extname).to_s + ".spec.js")
|
|
207
|
+
spec_path = Brut.container.js_specs_dir / relative_path_as_spec
|
|
208
|
+
[ path, spec_path ]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
existing_files = files_to_create.select { |_,spec|
|
|
212
|
+
spec.exist?
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if existing_files.any? && !global_options.overwrite?
|
|
216
|
+
relative_paths = existing_files.map { |_,pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
217
|
+
err.puts "Some files to be generated already exist. Set --overwrite to overwrite them:"
|
|
218
|
+
relative_paths.each do |file|
|
|
219
|
+
err.puts file
|
|
220
|
+
end
|
|
221
|
+
return 1
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
files_to_create.each do |source_file, spec_file|
|
|
225
|
+
source_class = source_file.basename(source_file.extname)
|
|
226
|
+
tag_name = File.read(source_file).split(/\n/).map { |line|
|
|
227
|
+
if line =~ /static\s+tagName\s*=\s*\"([^"]+)\"/
|
|
228
|
+
"<#{$1}>"
|
|
229
|
+
else
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
}.compact.first
|
|
233
|
+
description = tag_name || source_class
|
|
234
|
+
code = %{import { withHTML } from "brut-js/testing/index.js"
|
|
235
|
+
|
|
236
|
+
describe("#{description}", () => {
|
|
237
|
+
withHTML(`
|
|
238
|
+
#{ tag_name ? "#{tag_name}" : "<!-- HTML here -->" }
|
|
239
|
+
#{ tag_name ? "#{tag_name.gsub(/^</,'</')}" : "" }
|
|
240
|
+
`).test("description here", ({document,window,assert}) => {
|
|
241
|
+
assert.fail("test goes here")
|
|
242
|
+
})
|
|
243
|
+
})}
|
|
244
|
+
if global_options.dry_run?
|
|
245
|
+
out.puts "Would generate this code:\n\n#{code}"
|
|
246
|
+
else
|
|
247
|
+
File.open(spec_file, "w") do |file|
|
|
248
|
+
file.puts code
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
0
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|