snowpack 1.0.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.tool-versions +1 -0
- data/.travis.yml +16 -0
- data/Brewfile +2 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +116 -0
- data/Rakefile +6 -0
- data/bin/bootstrap +25 -0
- data/exe/snowpack +7 -0
- data/lib/snowpack/application.rb +98 -0
- data/lib/snowpack/application_cli.rb +39 -0
- data/lib/snowpack/cli/application/cli.rb +32 -0
- data/lib/snowpack/cli/application/command.rb +53 -0
- data/lib/snowpack/cli/application/commands/assets/clobber.rb +31 -0
- data/lib/snowpack/cli/application/commands/assets/compile.rb +32 -0
- data/lib/snowpack/cli/application/commands/console.rb +55 -0
- data/lib/snowpack/cli/application/commands/db/create.rb +33 -0
- data/lib/snowpack/cli/application/commands/db/create_migration.rb +38 -0
- data/lib/snowpack/cli/application/commands/db/drop.rb +33 -0
- data/lib/snowpack/cli/application/commands/db/migrate.rb +47 -0
- data/lib/snowpack/cli/application/commands/db/reset.rb +28 -0
- data/lib/snowpack/cli/application/commands/db/rollback.rb +54 -0
- data/lib/snowpack/cli/application/commands/db/sample_data.rb +41 -0
- data/lib/snowpack/cli/application/commands/db/seed.rb +41 -0
- data/lib/snowpack/cli/application/commands/db/structure/dump.rb +38 -0
- data/lib/snowpack/cli/application/commands/db/utils/database.rb +76 -0
- data/lib/snowpack/cli/application/commands/db/utils/database_config.rb +49 -0
- data/lib/snowpack/cli/application/commands/db/version.rb +37 -0
- data/lib/snowpack/cli/application/commands/generate/slice.rb +32 -0
- data/lib/snowpack/cli/application/commands/routes/update.rb +35 -0
- data/lib/snowpack/cli/application/commands.rb +27 -0
- data/lib/snowpack/cli/command.rb +48 -0
- data/lib/snowpack/cli/standalone/cli.rb +14 -0
- data/lib/snowpack/cli/standalone/commands/new.rb +33 -0
- data/lib/snowpack/cli/standalone/commands.rb +13 -0
- data/lib/snowpack/components/formalist.rb +27 -0
- data/lib/snowpack/components/persistence.rb +56 -0
- data/lib/snowpack/components.rb +5 -0
- data/lib/snowpack/console/context.rb +32 -0
- data/lib/snowpack/console/plugins/relation_readers.rb +17 -0
- data/lib/snowpack/console/plugins/slice_readers.rb +33 -0
- data/lib/snowpack/generator.rb +57 -0
- data/lib/snowpack/generators/application/generator.rb +67 -0
- data/lib/snowpack/generators/application/templates/.env-example.tt +10 -0
- data/lib/snowpack/generators/application/templates/.env.test-example.tt +1 -0
- data/lib/snowpack/generators/application/templates/.gitignore +20 -0
- data/lib/snowpack/generators/application/templates/.rspec +2 -0
- data/lib/snowpack/generators/application/templates/Brewfile +4 -0
- data/lib/snowpack/generators/application/templates/Gemfile.tt +46 -0
- data/lib/snowpack/generators/application/templates/Guardfile +7 -0
- data/lib/snowpack/generators/application/templates/Procfile.dev +3 -0
- data/lib/snowpack/generators/application/templates/Procfile.support +1 -0
- data/lib/snowpack/generators/application/templates/README.md.tt +45 -0
- data/lib/snowpack/generators/application/templates/Rakefile +5 -0
- data/lib/snowpack/generators/application/templates/bin/run +7 -0
- data/lib/snowpack/generators/application/templates/config/application.rb.tt +19 -0
- data/lib/snowpack/generators/application/templates/config/puma.rb.tt +14 -0
- data/lib/snowpack/generators/application/templates/config/routes.rb.tt +15 -0
- data/lib/snowpack/generators/application/templates/config.ru +5 -0
- data/lib/snowpack/generators/application/templates/db/migrate/.keep +0 -0
- data/lib/snowpack/generators/application/templates/db/sample_data.rb +1 -0
- data/lib/snowpack/generators/application/templates/db/seed.rb +1 -0
- data/lib/snowpack/generators/application/templates/lib/__application_path__/operation.rb.tt +12 -0
- data/lib/snowpack/generators/application/templates/lib/__application_path__/persistence/relations/.keep +0 -0
- data/lib/snowpack/generators/application/templates/lib/__application_path__/types.rb.tt +11 -0
- data/lib/snowpack/generators/application/templates/lib/__application_path__/web/action.rb.tt +56 -0
- data/lib/snowpack/generators/application/templates/lib/__application_path__/web/view/context.rb.tt +31 -0
- data/lib/snowpack/generators/application/templates/lib/hanami/action/csrf_protection.rb +225 -0
- data/lib/snowpack/generators/application/templates/log/.keep +0 -0
- data/lib/snowpack/generators/application/templates/package.json.tt +25 -0
- data/lib/snowpack/generators/application/templates/public/.keep +0 -0
- data/lib/snowpack/generators/application/templates/script/bootstrap +32 -0
- data/lib/snowpack/generators/application/templates/script/console +26 -0
- data/lib/snowpack/generators/application/templates/script/server +13 -0
- data/lib/snowpack/generators/application/templates/script/setup +27 -0
- data/lib/snowpack/generators/application/templates/script/support +9 -0
- data/lib/snowpack/generators/application/templates/script/test +19 -0
- data/lib/snowpack/generators/application/templates/script/update +13 -0
- data/lib/snowpack/generators/application/templates/spec/spec_helper.rb +13 -0
- data/lib/snowpack/generators/application/templates/spec/suite/.keep +0 -0
- data/lib/snowpack/generators/application/templates/spec/support/suite.rb +9 -0
- data/lib/snowpack/generators/application/templates/system/__application_path__/import.rb.tt +5 -0
- data/lib/snowpack/generators/application/templates/system/boot/assets.rb.tt +17 -0
- data/lib/snowpack/generators/application/templates/system/boot/logger.rb.tt +10 -0
- data/lib/snowpack/generators/application/templates/system/boot/monitor.rb.tt +9 -0
- data/lib/snowpack/generators/application/templates/system/boot/persistence.rb.tt +9 -0
- data/lib/snowpack/generators/application/templates/system/boot/settings.rb.tt +18 -0
- data/lib/snowpack/generators/application/templates/system/boot/web.rb.tt +9 -0
- data/lib/snowpack/generators/slice/generator.rb +62 -0
- data/lib/snowpack/generators/slice/templates/lib/__slice_path__/.keep +0 -0
- data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/action.rb.tt +10 -0
- data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/actions/.keep +0 -0
- data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/view.rb.tt +17 -0
- data/lib/snowpack/generators/slice/templates/lib/__slice_path__/web/views/.keep +0 -0
- data/lib/snowpack/generators/slice/templates/system/__slice_path__/import.rb.tt +9 -0
- data/lib/snowpack/generators/slice/templates/system/__slice_path__/slice.rb.tt +10 -0
- data/lib/snowpack/instrumentation/appsignal/appsignal_ext.rb +13 -0
- data/lib/snowpack/instrumentation/appsignal/que.rb +70 -0
- data/lib/snowpack/instrumentation/appsignal/rack.rb +84 -0
- data/lib/snowpack/roda/web.rb +47 -0
- data/lib/snowpack/slice.rb +85 -0
- data/lib/snowpack/test/suite.rb +164 -0
- data/lib/snowpack/test/tasks.rake +40 -0
- data/lib/snowpack/test_tasks.rb +4 -0
- data/lib/snowpack/types.rb +9 -0
- data/lib/snowpack/version.rb +5 -0
- data/lib/snowpack/view/part_builder.rb +45 -0
- data/lib/snowpack/view/parts/pager.rb +107 -0
- data/lib/snowpack/view/parts/paginated.rb +22 -0
- data/lib/snowpack/web/application.rb +38 -0
- data/lib/snowpack/web/assets.rb +56 -0
- data/lib/snowpack/web/endpoint_resolver.rb +66 -0
- data/lib/snowpack/web/form.rb +38 -0
- data/lib/snowpack/web/plugin.rb +44 -0
- data/lib/snowpack/web/rack_logger.rb +73 -0
- data/lib/snowpack/web/router.rb +58 -0
- data/lib/snowpack/web.rb +20 -0
- data/lib/snowpack.rb +27 -0
- data/snowpack.gemspec +34 -0
- metadata +327 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "appsignal"
|
4
|
+
require_relative "appsignal_ext"
|
5
|
+
require "securerandom"
|
6
|
+
|
7
|
+
# Remove Appsignal's own Que integration hook, so the Snowpack-hosted
|
8
|
+
# integration can be included manually.
|
9
|
+
Appsignal::Hooks.hooks.delete :que
|
10
|
+
|
11
|
+
module Snowpack
|
12
|
+
module Instrumentation
|
13
|
+
module Appsignal
|
14
|
+
module Que
|
15
|
+
# Enable like so:
|
16
|
+
#
|
17
|
+
# Que.error_notifier = Snowpack::Instrumentation::Appsignal::Que::ErrorNotifier
|
18
|
+
|
19
|
+
# Actually, why is this even necessry?
|
20
|
+
ErrorNotifier = -> error, _job {
|
21
|
+
::Appsignal::Transaction.current.set_error(error)
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.included(base)
|
25
|
+
base.class_eval do
|
26
|
+
def _run_with_appsignal(*args)
|
27
|
+
env = {
|
28
|
+
:metadata => {
|
29
|
+
:id => attrs[:job_id],
|
30
|
+
:queue => attrs[:queue],
|
31
|
+
:run_at => attrs[:run_at].to_s,
|
32
|
+
:priority => attrs[:priority],
|
33
|
+
:attempts => attrs[:error_count].to_i
|
34
|
+
},
|
35
|
+
:params => attrs[:args]
|
36
|
+
}
|
37
|
+
|
38
|
+
request = ::Appsignal::Transaction::GenericRequest.new(env)
|
39
|
+
|
40
|
+
transaction = ::Appsignal::Transaction.create(
|
41
|
+
SecureRandom.uuid,
|
42
|
+
::Appsignal::Transaction::BACKGROUND_JOB,
|
43
|
+
request,
|
44
|
+
)
|
45
|
+
|
46
|
+
transaction.discard! unless instrument?
|
47
|
+
|
48
|
+
begin
|
49
|
+
::Appsignal.instrument("perform_job.que") { _run_without_appsignal(*args) }
|
50
|
+
rescue Exception => error
|
51
|
+
transaction.set_error(error)
|
52
|
+
raise error
|
53
|
+
ensure
|
54
|
+
transaction.set_action "#{attrs[:job_class]}#run"
|
55
|
+
::Appsignal::Transaction.complete_current!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :_run_without_appsignal, :_run
|
60
|
+
alias_method :_run, :_run_with_appsignal
|
61
|
+
|
62
|
+
def instrument?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# use Snowpack::Instrumentation::Rack::Appsignal, {
|
4
|
+
# sample: 0.2,
|
5
|
+
# filters: {
|
6
|
+
# "/some/exact/route" => 0.05, # <- custom sample rate (5%) for specific URL
|
7
|
+
# %r{/another/.+/route} => false, # <- do not instrument this URL, n.b. match via Regexp
|
8
|
+
# },
|
9
|
+
# }
|
10
|
+
|
11
|
+
require "appsignal"
|
12
|
+
require_relative "appsignal_ext"
|
13
|
+
require "rack"
|
14
|
+
require "securerandom"
|
15
|
+
|
16
|
+
module Snowpack
|
17
|
+
module Instrumentation
|
18
|
+
module Appsignal
|
19
|
+
class Rack
|
20
|
+
attr_reader :app, :options
|
21
|
+
|
22
|
+
def initialize(app, **options)
|
23
|
+
@app = app
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
if ::Appsignal.active?
|
29
|
+
call_with_appsignal_monitoring(env)
|
30
|
+
else
|
31
|
+
app.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def call_with_appsignal_monitoring(env)
|
38
|
+
request = ::Rack::Request.new(env)
|
39
|
+
|
40
|
+
transaction = ::Appsignal::Transaction.create(
|
41
|
+
SecureRandom.uuid,
|
42
|
+
::Appsignal::Transaction::HTTP_REQUEST,
|
43
|
+
request,
|
44
|
+
)
|
45
|
+
|
46
|
+
transaction.discard! unless sample?(request)
|
47
|
+
|
48
|
+
begin
|
49
|
+
::Appsignal.instrument "process_action.generic" do
|
50
|
+
app.call(env)
|
51
|
+
end
|
52
|
+
rescue Exception => error
|
53
|
+
transaction.set_error(error)
|
54
|
+
raise error
|
55
|
+
ensure
|
56
|
+
if env["appsignal.route"]
|
57
|
+
transaction.set_action_if_nil(env["appsignal.route"])
|
58
|
+
else
|
59
|
+
transaction.set_action_if_nil("unknown")
|
60
|
+
end
|
61
|
+
|
62
|
+
transaction.set_metadata("path", request.path)
|
63
|
+
transaction.set_metadata("method", request.request_method)
|
64
|
+
|
65
|
+
transaction.set_http_or_background_queue_start
|
66
|
+
::Appsignal::Transaction.complete_current!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def sample?(request)
|
71
|
+
rate = options.fetch(:sample, 1)
|
72
|
+
|
73
|
+
options.fetch(:filters, {}).each do |match, sample|
|
74
|
+
rate = sample and break if request.path.match?(match)
|
75
|
+
end
|
76
|
+
|
77
|
+
rate = 1 if rate == true
|
78
|
+
|
79
|
+
rate && rand <= rate
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO wrap in begin/rescue loaderror and raise a meaningful exception
|
4
|
+
require "roda"
|
5
|
+
require "roda/plugins/flow"
|
6
|
+
|
7
|
+
module Snowpack
|
8
|
+
module Roda
|
9
|
+
# Mostly copied from Dry::Web::Roda::Application
|
10
|
+
class Web < ::Roda
|
11
|
+
extend Dry::Configurable
|
12
|
+
|
13
|
+
setting :container, reader: true
|
14
|
+
setting :routes
|
15
|
+
|
16
|
+
plugin :flow
|
17
|
+
|
18
|
+
# Disable this so we can better control when it's used
|
19
|
+
#
|
20
|
+
# def self.configure(&block)
|
21
|
+
# super.tap do
|
22
|
+
# use(container[:rack_monitor]) if container.config.listeners
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
|
26
|
+
def self.resolve(name)
|
27
|
+
container[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.[](name)
|
31
|
+
container[name]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.load_routes!
|
35
|
+
Dir[root.join("#{config.routes}/**/*.rb")].each { |f| require f }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.root
|
39
|
+
container.config.root
|
40
|
+
end
|
41
|
+
|
42
|
+
# def notifications
|
43
|
+
# self.class[:notifications]
|
44
|
+
# end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "../snowpack"
|
5
|
+
|
6
|
+
module Snowpack
|
7
|
+
class Slice < Dry::System::Container
|
8
|
+
use :env, inferrer: -> { ENV.fetch('RACK_ENV', 'development').to_sym }
|
9
|
+
|
10
|
+
def self.inherited(klass)
|
11
|
+
super
|
12
|
+
|
13
|
+
if klass.superclass == Snowpack::Slice
|
14
|
+
raise "Snowpack.application not configured yet" unless Snowpack.application?
|
15
|
+
|
16
|
+
app = Snowpack.application
|
17
|
+
|
18
|
+
klass.instance_variable_set :@application, app
|
19
|
+
|
20
|
+
klass.config.env = app.env
|
21
|
+
klass.config.name = klass.slice_name.to_sym
|
22
|
+
klass.config.auto_register = [File.join("lib", klass.slice_namespace_path)]
|
23
|
+
klass.config.default_namespace = klass.slice_namespace_identifier_prefix
|
24
|
+
|
25
|
+
slice_path = File.join(app.config.root, app.config.slices_dir, klass.slice_name)
|
26
|
+
if File.directory?(slice_path)
|
27
|
+
klass.config.root = slice_path if File.directory?(slice_path)
|
28
|
+
klass.load_paths! "lib"
|
29
|
+
end
|
30
|
+
|
31
|
+
klass.import application: app
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.import_slice(*slices)
|
36
|
+
@slice_imports ||= []
|
37
|
+
@slice_imports += slices
|
38
|
+
@slice_imports.uniq!
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.application
|
43
|
+
@application
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.boot!
|
47
|
+
finalize!
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.finalize!
|
51
|
+
# Force `after :configure` hooks to run
|
52
|
+
configure do; end
|
53
|
+
|
54
|
+
super do
|
55
|
+
Array(@slice_imports).each do |slice|
|
56
|
+
import slice => application.slices.fetch(slice)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
MODULE_DELIMITER = "::"
|
64
|
+
|
65
|
+
def self.slice_namespace
|
66
|
+
inflector.constantize(name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER))
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.slice_namespace_path
|
70
|
+
inflector.underscore(slice_namespace.to_s)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.slice_namespace_identifier_prefix
|
74
|
+
slice_namespace_path.gsub("/", ".")
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.slice_name
|
78
|
+
inflector.underscore(slice_namespace.to_s.split(MODULE_DELIMITER).last)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.inflector
|
82
|
+
application[:inflector]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require_relative "../../snowpack"
|
7
|
+
|
8
|
+
module Snowpack
|
9
|
+
module Test
|
10
|
+
module Helpers
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def suite
|
14
|
+
@__suite__ ||= RSpec.configuration.suite
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Suite
|
19
|
+
DB_CLEANUP_PATH_REGEX = /(features|integration)/
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_accessor :current
|
23
|
+
|
24
|
+
def root
|
25
|
+
@__root__ ||= Pathname(Dir.pwd).join("spec").freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def instance
|
29
|
+
@__instance__ ||= new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.inherited(klass)
|
34
|
+
super
|
35
|
+
Suite.current = klass
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.configure
|
39
|
+
RSpec.configure do |config|
|
40
|
+
config.add_setting :suite
|
41
|
+
|
42
|
+
config.suite = self.new
|
43
|
+
|
44
|
+
config.include Helpers
|
45
|
+
|
46
|
+
config.disable_monkey_patching!
|
47
|
+
|
48
|
+
config.expect_with :rspec do |expectations|
|
49
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
50
|
+
end
|
51
|
+
|
52
|
+
config.mock_with :rspec do |mocks|
|
53
|
+
mocks.verify_partial_doubles = true
|
54
|
+
end
|
55
|
+
|
56
|
+
config.filter_run :focus
|
57
|
+
config.run_all_when_everything_filtered = true
|
58
|
+
|
59
|
+
config.example_status_persistence_file_path = root.join("../tmp").realpath.join("spec/examples.txt").to_s
|
60
|
+
|
61
|
+
if config.files_to_run.one?
|
62
|
+
config.default_formatter = "doc"
|
63
|
+
end
|
64
|
+
|
65
|
+
config.profile_examples = 10
|
66
|
+
|
67
|
+
config.order = :random
|
68
|
+
|
69
|
+
Kernel.srand config.seed
|
70
|
+
|
71
|
+
yield(config) if block_given?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :application, :root
|
76
|
+
|
77
|
+
def initialize(application: Snowpack.application, root: self.class.root.join("suite"))
|
78
|
+
@application = application
|
79
|
+
@root = root
|
80
|
+
|
81
|
+
application.load_slices
|
82
|
+
end
|
83
|
+
|
84
|
+
def start_coverage
|
85
|
+
return unless coverage?
|
86
|
+
|
87
|
+
require 'simplecov'
|
88
|
+
|
89
|
+
if parallel?
|
90
|
+
SimpleCov.command_name(test_group_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
SimpleCov.start do
|
94
|
+
add_filter '/spec/'
|
95
|
+
add_filter '/system/'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def coverage_threshold
|
100
|
+
ENV.fetch('COVERAGE_THRESHOLD').to_f.round
|
101
|
+
end
|
102
|
+
|
103
|
+
def current_coverage
|
104
|
+
data = JSON.load(application.root.join('coverage/.last_run.json'))
|
105
|
+
data.fetch('result').fetch('covered_percent').to_f.round
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_group_name
|
109
|
+
@__test_group_name__ ||= "test_suite_#{build_idx}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def chdir(name)
|
113
|
+
self.class.new(
|
114
|
+
application: application,
|
115
|
+
root: root.join(name.to_s),
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
def files
|
120
|
+
dirs.map { |dir| dir.join("**/*_spec.rb") }.flat_map { |path| Dir[path] }.sort
|
121
|
+
end
|
122
|
+
|
123
|
+
def groups
|
124
|
+
dirs.map(&:basename).map(&:to_s).map(&:to_sym).sort
|
125
|
+
end
|
126
|
+
|
127
|
+
def dirs
|
128
|
+
Dir[root.join("*")].map(&Kernel.method(:Pathname)).select(&:directory?)
|
129
|
+
end
|
130
|
+
|
131
|
+
def build_idx
|
132
|
+
ENV.fetch('CIRCLE_NODE_INDEX', -1).to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def coverage?
|
136
|
+
ENV['COVERAGE'] == 'true'
|
137
|
+
end
|
138
|
+
|
139
|
+
def ci?
|
140
|
+
!ENV['CIRCLECI'].nil?
|
141
|
+
end
|
142
|
+
|
143
|
+
def parallel?
|
144
|
+
ENV['CIRCLE_NODE_TOTAL'].to_i > 1
|
145
|
+
end
|
146
|
+
|
147
|
+
def capybara_server_port
|
148
|
+
3001
|
149
|
+
end
|
150
|
+
|
151
|
+
def log_dir
|
152
|
+
application.root.join("log").to_s
|
153
|
+
end
|
154
|
+
|
155
|
+
def tmp_dir
|
156
|
+
application.root.join("tmp").to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
def clean_db?(example)
|
160
|
+
DB_CLEANUP_PATH_REGEX.match?(example.metadata[:file_path])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
begin
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "snowpack/test/suite"
|
4
|
+
|
5
|
+
require Snowpack::Test::Suite.root.join("support/suite")
|
6
|
+
suite = Snowpack::Test::Suite.current.new
|
7
|
+
|
8
|
+
desc "Run all specs"
|
9
|
+
RSpec::Core::RakeTask.new :spec do |t|
|
10
|
+
opts = ["#{suite.root} --pattern **/*_spec.rb"]
|
11
|
+
|
12
|
+
if suite.ci?
|
13
|
+
opts << "--format RspecJunitFormatter"
|
14
|
+
opts << "--out /tmp/test-results/rspec.xml"
|
15
|
+
opts << "--format progress"
|
16
|
+
end
|
17
|
+
|
18
|
+
t.rspec_opts = opts.join(" ")
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :spec do
|
22
|
+
suite.groups.each do |group|
|
23
|
+
desc "Run #{group} specs"
|
24
|
+
RSpec::Core::RakeTask.new(group) do |t|
|
25
|
+
t.rspec_opts = "#{suite.chdir(group).root} --pattern **/*_spec.rb"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Verify coverage'
|
30
|
+
task :verify_coverage do
|
31
|
+
if suite.current_coverage.to_i < suite.coverage_threshold
|
32
|
+
puts "Coverage too low. Current: #{suite.current_coverage}%; Expected min: #{suite.coverage_threshold}%"
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task default: [:spec]
|
39
|
+
rescue LoadError; end
|
40
|
+
# FIXME ^ make that way more specific
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/view/part_builder"
|
4
|
+
require "snowpack/view/parts/pager"
|
5
|
+
require_relative "parts/paginated"
|
6
|
+
|
7
|
+
module Snowpack
|
8
|
+
module View
|
9
|
+
class PartBuilder < Dry::View::PartBuilder
|
10
|
+
def call(name, value, **options)
|
11
|
+
if paginated_collection?(value)
|
12
|
+
build_paginated_collection_part(name, value, **options)
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def paginated_collection?(value)
|
21
|
+
value.respond_to?(:to_a) && value.respond_to?(:pager)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_paginated_collection_part(name, value, **options)
|
25
|
+
pager_part = build_part(:pager, value.pager, as: Snowpack::View::Parts::Pager)
|
26
|
+
|
27
|
+
collection_as = collection_options(name: name, **options)[:as]
|
28
|
+
item_name, item_as = collection_item_options(name: name, **options).values_at(:name, :as)
|
29
|
+
|
30
|
+
arr = value.to_ary.map { |obj|
|
31
|
+
call(item_name, obj, **options.merge(as: item_as))
|
32
|
+
}
|
33
|
+
|
34
|
+
collection_part_class = part_class(name: name, fallback_class: Parts::Paginated, **options.merge(as: collection_as))
|
35
|
+
|
36
|
+
collection_part_class.new(
|
37
|
+
name: name,
|
38
|
+
value: arr,
|
39
|
+
pager: pager_part,
|
40
|
+
render_env: render_env,
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/view/part"
|
4
|
+
require "rack/utils"
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
module Snowpack
|
8
|
+
module View
|
9
|
+
module Parts
|
10
|
+
class Pager < Dry::View::Part
|
11
|
+
attr_reader :part_args
|
12
|
+
attr_reader :buffer
|
13
|
+
|
14
|
+
def initialize(buffer: 3, **part_args)
|
15
|
+
@part_args = part_args
|
16
|
+
@buffer = buffer
|
17
|
+
|
18
|
+
super(**part_args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def with(**options)
|
22
|
+
new_options = {
|
23
|
+
buffer: buffer
|
24
|
+
}.merge(options)
|
25
|
+
|
26
|
+
self.class.new(**new_options, **part_args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def first_page
|
30
|
+
1
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_page?
|
34
|
+
current_page == first_page
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_page
|
38
|
+
total_pages
|
39
|
+
end
|
40
|
+
|
41
|
+
def last_page?
|
42
|
+
current_page == last_page
|
43
|
+
end
|
44
|
+
|
45
|
+
def prev_gap?
|
46
|
+
current_page - buffer > first_page
|
47
|
+
end
|
48
|
+
|
49
|
+
def next_gap?
|
50
|
+
current_page + buffer < last_page
|
51
|
+
end
|
52
|
+
|
53
|
+
def single_page?
|
54
|
+
total_pages <= 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_page_numbers
|
58
|
+
(first_page..last_page).to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
def each_entry
|
62
|
+
return to_enum(:each_entry) unless block_given?
|
63
|
+
|
64
|
+
entry_numbers.each do |number|
|
65
|
+
yield Entry.new(number, current: number == current_page)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def url_for_page(page)
|
70
|
+
uri = URI(_context.fullpath)
|
71
|
+
params = Rack::Utils.parse_query(uri.query).merge("page" => page.to_s)
|
72
|
+
uri.query = Rack::Utils.build_query(params)
|
73
|
+
uri.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def entry_numbers
|
79
|
+
left_edge = current_page - buffer
|
80
|
+
left_edge = 1 if left_edge < 1
|
81
|
+
|
82
|
+
right_edge = current_page + buffer
|
83
|
+
right_edge = total_pages if right_edge > total_pages
|
84
|
+
|
85
|
+
left_edge.upto(right_edge).to_a
|
86
|
+
end
|
87
|
+
|
88
|
+
class Entry
|
89
|
+
attr_reader :number
|
90
|
+
|
91
|
+
def initialize(number, current:)
|
92
|
+
@number = number
|
93
|
+
@current = current
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
number.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
def current?
|
101
|
+
!!@current
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/view/part"
|
4
|
+
|
5
|
+
module Snowpack
|
6
|
+
module View
|
7
|
+
module Parts
|
8
|
+
class Paginated < Dry::View::Part
|
9
|
+
attr_reader :pager
|
10
|
+
|
11
|
+
def initialize(pager:, **part_args)
|
12
|
+
@pager = pager
|
13
|
+
super(**part_args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def pagination(locals: {}, **options)
|
17
|
+
pager.with(**options).render(:pagination, **locals)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|