karafka 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +68 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +202 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +216 -0
- data/MIT-LICENCE +18 -0
- data/README.md +831 -0
- data/Rakefile +17 -0
- data/bin/karafka +7 -0
- data/karafka.gemspec +34 -0
- data/lib/karafka.rb +73 -0
- data/lib/karafka/app.rb +45 -0
- data/lib/karafka/base_controller.rb +162 -0
- data/lib/karafka/base_responder.rb +118 -0
- data/lib/karafka/base_worker.rb +41 -0
- data/lib/karafka/capistrano.rb +2 -0
- data/lib/karafka/capistrano/karafka.cap +84 -0
- data/lib/karafka/cli.rb +52 -0
- data/lib/karafka/cli/base.rb +74 -0
- data/lib/karafka/cli/console.rb +23 -0
- data/lib/karafka/cli/flow.rb +46 -0
- data/lib/karafka/cli/info.rb +26 -0
- data/lib/karafka/cli/install.rb +45 -0
- data/lib/karafka/cli/routes.rb +39 -0
- data/lib/karafka/cli/server.rb +59 -0
- data/lib/karafka/cli/worker.rb +26 -0
- data/lib/karafka/connection/consumer.rb +29 -0
- data/lib/karafka/connection/listener.rb +54 -0
- data/lib/karafka/connection/message.rb +17 -0
- data/lib/karafka/connection/topic_consumer.rb +48 -0
- data/lib/karafka/errors.rb +50 -0
- data/lib/karafka/fetcher.rb +40 -0
- data/lib/karafka/helpers/class_matcher.rb +77 -0
- data/lib/karafka/helpers/multi_delegator.rb +31 -0
- data/lib/karafka/loader.rb +77 -0
- data/lib/karafka/logger.rb +52 -0
- data/lib/karafka/monitor.rb +82 -0
- data/lib/karafka/params/interchanger.rb +33 -0
- data/lib/karafka/params/params.rb +102 -0
- data/lib/karafka/patches/dry/configurable/config.rb +37 -0
- data/lib/karafka/process.rb +61 -0
- data/lib/karafka/responders/builder.rb +33 -0
- data/lib/karafka/responders/topic.rb +43 -0
- data/lib/karafka/responders/usage_validator.rb +59 -0
- data/lib/karafka/routing/builder.rb +89 -0
- data/lib/karafka/routing/route.rb +80 -0
- data/lib/karafka/routing/router.rb +38 -0
- data/lib/karafka/server.rb +53 -0
- data/lib/karafka/setup/config.rb +57 -0
- data/lib/karafka/setup/configurators/base.rb +33 -0
- data/lib/karafka/setup/configurators/celluloid.rb +20 -0
- data/lib/karafka/setup/configurators/sidekiq.rb +34 -0
- data/lib/karafka/setup/configurators/water_drop.rb +19 -0
- data/lib/karafka/setup/configurators/worker_glass.rb +13 -0
- data/lib/karafka/status.rb +23 -0
- data/lib/karafka/templates/app.rb.example +26 -0
- data/lib/karafka/templates/application_controller.rb.example +5 -0
- data/lib/karafka/templates/application_responder.rb.example +9 -0
- data/lib/karafka/templates/application_worker.rb.example +12 -0
- data/lib/karafka/templates/config.ru.example +13 -0
- data/lib/karafka/templates/sidekiq.yml.example +26 -0
- data/lib/karafka/version.rb +6 -0
- data/lib/karafka/workers/builder.rb +49 -0
- data/log/.gitkeep +0 -0
- metadata +267 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Karafka framework Cli
|
3
|
+
class Cli
|
4
|
+
# Worker Karafka Cli action
|
5
|
+
class Worker < Base
|
6
|
+
desc 'Start the Karafka Sidekiq worker (short-cut alias: "w")'
|
7
|
+
option aliases: 'w'
|
8
|
+
|
9
|
+
# Start the Karafka Sidekiq worker
|
10
|
+
# @param params [Array<String>] additional params that will be passed to sidekiq, that way we
|
11
|
+
# can override any default Karafka settings
|
12
|
+
def call(*params)
|
13
|
+
puts 'Starting Karafka worker'
|
14
|
+
config = "-C #{Karafka::App.root.join('config/sidekiq.yml')}"
|
15
|
+
req = "-r #{Karafka.boot_file}"
|
16
|
+
env = "-e #{Karafka.env}"
|
17
|
+
|
18
|
+
cli.info
|
19
|
+
|
20
|
+
cmd = "bundle exec sidekiq #{env} #{req} #{config} #{params.join(' ')}"
|
21
|
+
puts(cmd)
|
22
|
+
exec(cmd)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Connection
|
3
|
+
# Class that consumes messages for which we listen
|
4
|
+
class Consumer
|
5
|
+
# Consumes a message (does something with it)
|
6
|
+
# It will execute a scheduling task from a proper controller based on a message topic
|
7
|
+
# @note This should be looped to obtain a constant listening
|
8
|
+
# @note We catch all the errors here, to make sure that none failures
|
9
|
+
# for a given consumption will affect other consumed messages
|
10
|
+
# If we would't catch it, it would propagate up until killing the Celluloid actor
|
11
|
+
# @param message [Kafka::FetchedMessage] message that was fetched by kafka
|
12
|
+
def consume(message)
|
13
|
+
controller = Karafka::Routing::Router.new(message.topic).build
|
14
|
+
# We wrap it around with our internal message format, so we don't pass around
|
15
|
+
# a raw Kafka message
|
16
|
+
controller.params = Message.new(message.topic, message.value)
|
17
|
+
|
18
|
+
Karafka.monitor.notice(self.class, controller.to_h)
|
19
|
+
|
20
|
+
controller.schedule
|
21
|
+
# This is on purpose - see the notes for this method
|
22
|
+
# rubocop:disable RescueException
|
23
|
+
rescue Exception => e
|
24
|
+
# rubocop:enable RescueException
|
25
|
+
Karafka.monitor.notice_error(self.class, e)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Connection
|
3
|
+
# A single listener that listens to incoming messages from a single route
|
4
|
+
# @note It does not loop on itself - it needs to be executed in a loop
|
5
|
+
# @note Listener itself does nothing with the message - it will return to the block
|
6
|
+
# a raw Kafka::FetchedMessage
|
7
|
+
class Listener
|
8
|
+
include Celluloid
|
9
|
+
|
10
|
+
execute_block_on_receiver :fetch_loop
|
11
|
+
|
12
|
+
attr_reader :route
|
13
|
+
|
14
|
+
# @return [Karafka::Connection::Listener] listener instance
|
15
|
+
def initialize(route)
|
16
|
+
@route = route
|
17
|
+
end
|
18
|
+
|
19
|
+
# Opens connection, gets messages and calls a block for each of the incoming messages
|
20
|
+
# @yieldparam [Karafka::BaseController] base controller descendant
|
21
|
+
# @yieldparam [Kafka::FetchedMessage] kafka fetched message
|
22
|
+
# @note This will yield with a raw message - no preprocessing or reformatting
|
23
|
+
# @note We catch all the errors here, so they don't affect other listeners (or this one)
|
24
|
+
# so we will be able to listen and consume other incoming messages.
|
25
|
+
# Since it is run inside Karafka::Connection::ActorCluster - catching all the exceptions
|
26
|
+
# won't crash the whole cluster. Here we mostly focus on catchin the exceptions related to
|
27
|
+
# Kafka connections / Internet connection issues / Etc. Business logic problems should not
|
28
|
+
# propagate this far
|
29
|
+
def fetch_loop(block)
|
30
|
+
topic_consumer.fetch_loop do |raw_message|
|
31
|
+
block.call(raw_message)
|
32
|
+
end
|
33
|
+
# This is on purpose - see the notes for this method
|
34
|
+
# rubocop:disable RescueException
|
35
|
+
rescue Exception => e
|
36
|
+
# rubocop:enable RescueException
|
37
|
+
Karafka.monitor.notice_error(self.class, e)
|
38
|
+
@topic_consumer&.stop
|
39
|
+
retry if @topic_consumer
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [Karafka::Connection::TopicConsumer] wrapped kafka consumer for a given topic
|
45
|
+
# consumption
|
46
|
+
# @note It adds consumer into Karafka::Server consumers pool for graceful shutdown on exit
|
47
|
+
def topic_consumer
|
48
|
+
@topic_consumer ||= TopicConsumer.new(@route).tap do |consumer|
|
49
|
+
Karafka::Server.consumers << consumer if Karafka::Server.consumers
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Namespace that encapsulates everything related to connections
|
3
|
+
module Connection
|
4
|
+
# Single incoming Kafka message instance wrapper
|
5
|
+
class Message
|
6
|
+
attr_reader :topic, :content
|
7
|
+
|
8
|
+
# @param topic [String] topic from which this message comes
|
9
|
+
# @param content [String] raw message content (not deserialized or anything) from Kafka
|
10
|
+
# @return [Karafka::Connection::Message] incoming message instance
|
11
|
+
def initialize(topic, content)
|
12
|
+
@topic = topic
|
13
|
+
@content = content
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Connection
|
3
|
+
# Class used as a wrapper around Ruby-Kafka to simplify additional
|
4
|
+
# features that we provide/might provide in future
|
5
|
+
class TopicConsumer
|
6
|
+
# Creates a queue consumer that will pull the data from Kafka
|
7
|
+
# @param [Karafka::Routing::Route] route details that will be used to build up a
|
8
|
+
# queue consumer instance
|
9
|
+
# @return [Karafka::Connection::QueueConsumer] queue consumer instance
|
10
|
+
def initialize(route)
|
11
|
+
@route = route
|
12
|
+
end
|
13
|
+
|
14
|
+
# Opens connection, gets messages and calls a block for each of the incoming messages
|
15
|
+
# @yieldparam [Kafka::FetchedMessage] kafka fetched message
|
16
|
+
# @note This will yield with a raw message - no preprocessing or reformatting
|
17
|
+
def fetch_loop
|
18
|
+
kafka_consumer.each_message do |message|
|
19
|
+
yield(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Gracefuly stops topic consumption
|
24
|
+
def stop
|
25
|
+
kafka_consumer.stop
|
26
|
+
@kafka_consumer = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @return [Kafka::Consumer] returns a ready to consume Kafka consumer
|
32
|
+
# that is set up to consume a given routes topic
|
33
|
+
def kafka_consumer
|
34
|
+
return @kafka_consumer if @kafka_consumer
|
35
|
+
|
36
|
+
kafka = Kafka.new(
|
37
|
+
seed_brokers: ::Karafka::App.config.kafka.hosts,
|
38
|
+
logger: ::Karafka.logger,
|
39
|
+
client_id: ::Karafka::App.config.name
|
40
|
+
)
|
41
|
+
|
42
|
+
@kafka_consumer = kafka.consumer(group_id: @route.group)
|
43
|
+
@kafka_consumer.subscribe(@route.topic)
|
44
|
+
@kafka_consumer
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Namespace used to encapsulate all the internal errors of Karafka
|
3
|
+
module Errors
|
4
|
+
# Base class for all the Karafka internal errors
|
5
|
+
class BaseError < StandardError; end
|
6
|
+
|
7
|
+
# Should be raised when we attemp to parse incoming params but parsing fails
|
8
|
+
# If this error (or its descendant) is detected, we will pass the raw message
|
9
|
+
# into params and proceed further
|
10
|
+
class ParserError < BaseError; end
|
11
|
+
|
12
|
+
# Raised when router receives topic name which does not correspond with any routes
|
13
|
+
# This should never happen because we listed only to topics defined in routes
|
14
|
+
# but theory is not always right. If you encounter this error - please contact
|
15
|
+
# Karafka maintainers
|
16
|
+
class NonMatchingRouteError < BaseError; end
|
17
|
+
|
18
|
+
# Raised when we have few controllers(inherited from Karafka::BaseController)
|
19
|
+
# with the same group name
|
20
|
+
class DuplicatedGroupError < BaseError; end
|
21
|
+
|
22
|
+
# Raised when we have few controllers(inherited from Karafka::BaseController)
|
23
|
+
# with the same topic name
|
24
|
+
class DuplicatedTopicError < BaseError; end
|
25
|
+
|
26
|
+
# Raised when we want to use topic name that has unsupported characters
|
27
|
+
class InvalidTopicName < BaseError; end
|
28
|
+
|
29
|
+
# Raised when we want to use group name that has unsupported characters
|
30
|
+
class InvalidGroupName < BaseError; end
|
31
|
+
|
32
|
+
# Raised when application does not have ApplicationWorker or other class that directly
|
33
|
+
# inherits from Karafka::BaseWorker
|
34
|
+
class BaseWorkerDescentantMissing < BaseError; end
|
35
|
+
|
36
|
+
# Raised when we want to use #respond_with in controllers but we didn't define
|
37
|
+
# (and we couldn't find) any appropriate responder for a given controller
|
38
|
+
class ResponderMissing < BaseError; end
|
39
|
+
|
40
|
+
# Raised when we want to use #respond_to in responders with a topic that we didn't register
|
41
|
+
class UnregisteredTopic < BaseError; end
|
42
|
+
|
43
|
+
# Raised when we send more than one message to a single topic but we didn't allow that when
|
44
|
+
# we were registering topic in a responder
|
45
|
+
class TopicMultipleUsage < BaseError; end
|
46
|
+
|
47
|
+
# Raised when we didn't use a topic that was defined as non-optional (required)
|
48
|
+
class UnusedResponderRequiredTopic < BaseError; end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Class used to run the Karafka consumer and handle shutting down, restarting etc
|
3
|
+
# @note Creating multiple fetchers will result in having multiple connections to the same
|
4
|
+
# topics, which means that if there are no partitions, it won't use them.
|
5
|
+
class Fetcher
|
6
|
+
# Starts listening on all the listeners asynchronously
|
7
|
+
# Fetch loop should never end, which means that we won't create more actor clusters
|
8
|
+
# so we don't have to terminate them
|
9
|
+
def fetch_loop
|
10
|
+
futures = listeners.map do |listener|
|
11
|
+
listener.future.public_send(:fetch_loop, consumer)
|
12
|
+
end
|
13
|
+
|
14
|
+
futures.map(&:value)
|
15
|
+
# If anything crashes here, we need to raise the error and crush the runner because it means
|
16
|
+
# that something really bad happened
|
17
|
+
rescue => e
|
18
|
+
Karafka.monitor.notice_error(self.class, e)
|
19
|
+
Karafka::App.stop!
|
20
|
+
raise e
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# @return [Array<Karafka::Connection::Listener>] listeners that will consume messages
|
26
|
+
def listeners
|
27
|
+
@listeners ||= App.routes.map do |route|
|
28
|
+
Karafka::Connection::Listener.new(route)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Proc] proc that should be processed when a message arrives
|
33
|
+
# @yieldparam message [Kafka::FetchedMessage] message from kafka (raw one)
|
34
|
+
def consumer
|
35
|
+
lambda do |message|
|
36
|
+
Karafka::Connection::Consumer.new.consume(message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Helpers
|
3
|
+
# Class used to autodetect corresponding classes that are internally inside Karafka framework
|
4
|
+
# It is used among others to match:
|
5
|
+
# controller => worker
|
6
|
+
# controller => responder
|
7
|
+
class ClassMatcher
|
8
|
+
# Regexp used to remove any non classy like characters that might be in the controller
|
9
|
+
# class name (if defined dynamically, etc)
|
10
|
+
CONSTANT_REGEXP = %r{[?!=+\-\*/\^\|&\[\]<>%~\#\:\s\(\)]}
|
11
|
+
|
12
|
+
# @param klass [Class] class to which we want to find a corresponding class
|
13
|
+
# @param from [String] what type of object is it (based on postfix name part)
|
14
|
+
# @param to [String] what are we looking for (based on a postfix name part)
|
15
|
+
# @example Controller that has a corresponding worker
|
16
|
+
# matcher = Karafka::Helpers::ClassMatcher.new(SuperController, 'Controller', 'Worker')
|
17
|
+
# matcher.match #=> SuperWorker
|
18
|
+
# @example Controller without a corresponding worker
|
19
|
+
# matcher = Karafka::Helpers::ClassMatcher.new(Super2Controller, 'Controller', 'Worker')
|
20
|
+
# matcher.match #=> nil
|
21
|
+
def initialize(klass, from:, to:)
|
22
|
+
@klass = klass
|
23
|
+
@from = from
|
24
|
+
@to = to
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Class] matched class
|
28
|
+
# @return [nil] nil if we couldn't find matching class
|
29
|
+
def match
|
30
|
+
return nil if name.empty?
|
31
|
+
return nil unless scope.const_defined?(name)
|
32
|
+
matching = scope.const_get(name)
|
33
|
+
same_scope?(matching) ? matching : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] name of a new class that we're looking for
|
37
|
+
# @note This method returns name of a class without a namespace
|
38
|
+
# @example From SuperController matching worker
|
39
|
+
# matcher.name #=> 'SuperWorker'
|
40
|
+
# @example From Namespaced::Super2Controller matching worker
|
41
|
+
# matcher.name #=> Super2Worker
|
42
|
+
def name
|
43
|
+
inflected = @klass.to_s.split('::').last.to_s
|
44
|
+
inflected.gsub!(@from, @to)
|
45
|
+
inflected.gsub!(CONSTANT_REGEXP, '')
|
46
|
+
inflected
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Class, Module] class or module in which we're looking for a matching
|
50
|
+
def scope
|
51
|
+
scope_of(@klass)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @param klass [Class] class for which we want to extract it's enclosing class/module
|
57
|
+
# @return [Class, Module] enclosing class/module
|
58
|
+
# @return [::Object] object if it was a root class
|
59
|
+
#
|
60
|
+
# @example Non-namespaced class
|
61
|
+
# scope_of(SuperClass) #=> Object
|
62
|
+
# @example Namespaced class
|
63
|
+
# scope_of(Abc::SuperClass) #=> Abc
|
64
|
+
def scope_of(klass)
|
65
|
+
enclosing = klass.to_s.split('::')[0...-1]
|
66
|
+
return ::Object if enclosing.empty?
|
67
|
+
::Object.const_get(enclosing.join('::'))
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param matching [Class] class of which scope we want to check
|
71
|
+
# @return [Boolean] true if the scope of class is the same as scope of matching
|
72
|
+
def same_scope?(matching)
|
73
|
+
scope == scope_of(matching)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Module containing classes and methods that provide some additional functionalities
|
3
|
+
module Helpers
|
4
|
+
# @note Taken from http://stackoverflow.com/questions/6407141
|
5
|
+
# Multidelegator is used to delegate calls to multiple targets
|
6
|
+
class MultiDelegator
|
7
|
+
# @param targets to which we want to delegate methods
|
8
|
+
#
|
9
|
+
def initialize(*targets)
|
10
|
+
@targets = targets
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# @param methods names that should be delegated to
|
15
|
+
# @example Delegate write and close to STDOUT and file
|
16
|
+
# Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
|
17
|
+
def delegate(*methods)
|
18
|
+
methods.each do |m|
|
19
|
+
define_method(m) do |*args|
|
20
|
+
@targets.map { |t| t.send(m, *args) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
alias to new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Loader for requiring all the files in a proper order
|
3
|
+
# Some files needs to be required before other, so it will
|
4
|
+
# try to figure it out. It will load 'base*' files first and then
|
5
|
+
# any other.
|
6
|
+
class Loader
|
7
|
+
# Order in which we want to load app files
|
8
|
+
DIRS = %w(
|
9
|
+
config/initializers
|
10
|
+
lib
|
11
|
+
app/helpers
|
12
|
+
app/inputs
|
13
|
+
app/decorators
|
14
|
+
app/models/concerns
|
15
|
+
app/models
|
16
|
+
app/responders
|
17
|
+
app/services
|
18
|
+
app/presenters
|
19
|
+
app/workers
|
20
|
+
app/controllers
|
21
|
+
app/aspects
|
22
|
+
app
|
23
|
+
).freeze
|
24
|
+
|
25
|
+
# Will load files in a proper order (based on DIRS)
|
26
|
+
# @param [String] root path from which we want to start
|
27
|
+
def load(root)
|
28
|
+
DIRS.each do |dir|
|
29
|
+
path = File.join(root, dir)
|
30
|
+
load!(path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Requires all the ruby files from one path in a proper order
|
35
|
+
# @param path [String] path (dir) from which we want to load ruby files in a proper order
|
36
|
+
# @note First we load all the base files that might be used in inheritance
|
37
|
+
def load!(path)
|
38
|
+
base_load!(path)
|
39
|
+
files_load!(path)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Requires all the ruby files from one relative path inside application directory
|
43
|
+
# @param relative_path [String] relative path (dir) to a file inside application directory
|
44
|
+
# from which we want to load ruby files in a proper order
|
45
|
+
def relative_load!(relative_path)
|
46
|
+
path = File.join(::Karafka.root, relative_path)
|
47
|
+
load!(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Loads all the base files
|
53
|
+
# @param path [String] path (dir) from which we want to load ruby base files in a proper order
|
54
|
+
def base_load!(path)
|
55
|
+
bases = File.join(path, '**/base*.rb')
|
56
|
+
Dir[bases].sort(&method(:base_sorter)).each(&method(:require))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Loads all other files (not base)
|
60
|
+
# @param path [String] path (dir) from which we want to load ruby files in a proper order
|
61
|
+
# @note Technically it will load the base files again but they are already loaded so nothing
|
62
|
+
# will happen
|
63
|
+
def files_load!(path)
|
64
|
+
files = File.join(path, '**/*.rb')
|
65
|
+
Dir[files].sort.each(&method(:require))
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Integer] order for sorting
|
69
|
+
# @note We need sort all base files based on their position in a file tree
|
70
|
+
# so all the files that are "higher" should be loaded first
|
71
|
+
# @param str1 [String] first string for comparison
|
72
|
+
# @param str2 [String] second string for comparison
|
73
|
+
def base_sorter(str1, str2)
|
74
|
+
str1.count('/') <=> str2.count('/')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|