archfiend 0.1.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/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- data/.gitignore +12 -0
- data/.rspec +4 -0
- data/.rubocop.yml +323 -0
- data/.travis.yml +11 -0
- data/CHANGES.md +12 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +122 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/ROADMAP.md +11 -0
- data/Rakefile +6 -0
- data/archfiend.gemspec +35 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/archfiend +4 -0
- data/lib/archfiend.rb +18 -0
- data/lib/archfiend/application.rb +132 -0
- data/lib/archfiend/cli.rb +9 -0
- data/lib/archfiend/generators/daemon.rb +144 -0
- data/lib/archfiend/generators/daemon/templates/.rspec +4 -0
- data/lib/archfiend/generators/daemon/templates/.rubocop.yml +323 -0
- data/lib/archfiend/generators/daemon/templates/Gemfile.tt +32 -0
- data/lib/archfiend/generators/daemon/templates/README.md.tt +23 -0
- data/lib/archfiend/generators/daemon/templates/Rakefile.tt +13 -0
- data/lib/archfiend/generators/daemon/templates/app/clockwork/clockwork.rb.tt +14 -0
- data/lib/archfiend/generators/daemon/templates/app/models/application_record.rb +3 -0
- data/lib/archfiend/generators/daemon/templates/app/subprocess_loops/foo_subprocess_loop.rb +13 -0
- data/lib/archfiend/generators/daemon/templates/app/thread_loops/bar_thread_loop.rb +8 -0
- data/lib/archfiend/generators/daemon/templates/bin/console +3 -0
- data/lib/archfiend/generators/daemon/templates/bin/start.tt +6 -0
- data/lib/archfiend/generators/daemon/templates/config/application.rb.tt +27 -0
- data/lib/archfiend/generators/daemon/templates/config/boot.rb.tt +3 -0
- data/lib/archfiend/generators/daemon/templates/config/daemon.rb.tt +18 -0
- data/lib/archfiend/generators/daemon/templates/config/database.yml.example.tt +26 -0
- data/lib/archfiend/generators/daemon/templates/config/environment.rb.tt +4 -0
- data/lib/archfiend/generators/daemon/templates/config/settings.yml.tt +8 -0
- data/lib/archfiend/generators/daemon/templates/config/settings/development.yml.tt +2 -0
- data/lib/archfiend/generators/daemon/templates/config/settings/production.yml.tt +2 -0
- data/lib/archfiend/generators/daemon/templates/config/settings/staging.yml.tt +5 -0
- data/lib/archfiend/generators/daemon/templates/config/settings/test.yml.tt +0 -0
- data/lib/archfiend/generators/daemon/templates/db/migrate/.gitkeep +0 -0
- data/lib/archfiend/generators/daemon/templates/lib/tasks/.gitkeep +0 -0
- data/lib/archfiend/generators/daemon/templates/log/.gitkeep +0 -0
- data/lib/archfiend/generators/daemon/templates/spec/factories/.gitkeep +0 -0
- data/lib/archfiend/generators/daemon/templates/spec/models/.gitkeep +0 -0
- data/lib/archfiend/generators/daemon/templates/spec/spec_helper.rb +43 -0
- data/lib/archfiend/generators/daemon/templates/spec/subprocess_loops/foo_subprocess_loop_spec.rb +5 -0
- data/lib/archfiend/generators/daemon/templates/spec/support/factory_bot.rb +9 -0
- data/lib/archfiend/generators/daemon/templates/spec/support/timecop.rb +9 -0
- data/lib/archfiend/generators/daemon/templates/spec/thread_loops/bar_thread_loop_spec.rb +5 -0
- data/lib/archfiend/generators/daemon/templates/tmp/.gitkeep +0 -0
- data/lib/archfiend/generators/extensions.rb +119 -0
- data/lib/archfiend/generators/options.rb +51 -0
- data/lib/archfiend/generators/utils.rb +37 -0
- data/lib/archfiend/logging.rb +38 -0
- data/lib/archfiend/logging/base_formatter.rb +35 -0
- data/lib/archfiend/logging/default_formatter.rb +12 -0
- data/lib/archfiend/logging/json_formatter.rb +21 -0
- data/lib/archfiend/logging/multi_logger.rb +31 -0
- data/lib/archfiend/shared_loop/runnable.rb +26 -0
- data/lib/archfiend/subprocess_loop.rb +46 -0
- data/lib/archfiend/thread_loop.rb +35 -0
- data/lib/archfiend/version.rb +3 -0
- data/scripts/travis/install +13 -0
- data/scripts/travis/script +43 -0
- metadata +280 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
source 'https://rubygems.org'
|
3
|
+
|
4
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
5
|
+
|
6
|
+
gem 'activesupport', require: 'active_support/time'
|
7
|
+
gem 'activerecord', require: 'active_record'
|
8
|
+
gem 'activerecord-migrations'
|
9
|
+
gem 'archfiend', github: 'toptal/archfiend'
|
10
|
+
gem 'clockwork', require: false
|
11
|
+
gem 'config'
|
12
|
+
gem 'daemons'
|
13
|
+
gem 'pg', '~> 0.21'
|
14
|
+
gem 'rbtrace', require: false
|
15
|
+
|
16
|
+
group :development, :test do
|
17
|
+
gem 'factory_bot', require: false
|
18
|
+
gem 'pry'
|
19
|
+
gem 'pry-stack_explorer'
|
20
|
+
gem 'pry-byebug'
|
21
|
+
gem 'pry-doc', require: false
|
22
|
+
gem 'rspec', require: false
|
23
|
+
gem 'rubocop', require: false
|
24
|
+
gem 'rubocop-rspec', require: false
|
25
|
+
gem 'shoulda-matchers', '~> 3.1'
|
26
|
+
gem 'timecop', require: false
|
27
|
+
gem 'webmock', require: false
|
28
|
+
end
|
29
|
+
|
30
|
+
group :test do
|
31
|
+
gem 'database_cleaner'
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# <%= camelized_daemon_name %>
|
2
|
+
|
3
|
+
## Running
|
4
|
+
|
5
|
+
### In the foreground
|
6
|
+
Start in the foreground (ex. development)
|
7
|
+
```bash
|
8
|
+
bin/start
|
9
|
+
```
|
10
|
+
|
11
|
+
### Daemonized
|
12
|
+
Start daemonized
|
13
|
+
```bash
|
14
|
+
bin/start -d
|
15
|
+
```
|
16
|
+
|
17
|
+
Kill daemonized:
|
18
|
+
```bash
|
19
|
+
kill `cat [daemon_name].pid`
|
20
|
+
```
|
21
|
+
|
22
|
+
### Console
|
23
|
+
`bin/console`
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/migrations/tasks'
|
3
|
+
ActiveRecord::Migrations.root = '.'
|
4
|
+
require File.expand_path(File.join('..', 'config', 'application.rb'), __FILE__)
|
5
|
+
|
6
|
+
<%= camelized_daemon_name %>.app.setup
|
7
|
+
|
8
|
+
DATABASE_ENV = <%= camelized_daemon_name %>.app.env
|
9
|
+
|
10
|
+
task :environment do
|
11
|
+
end
|
12
|
+
|
13
|
+
Dir["#{<%= camelized_daemon_name %>.root}/lib/tasks/*.rake"].each { |rake_file| load rake_file }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Uncomment the following code to have the Clockwork integration running in the daemon as a separate thread.
|
2
|
+
# https://github.com/Rykian/clockwork
|
3
|
+
|
4
|
+
# require 'clockwork'
|
5
|
+
#
|
6
|
+
# module Clockwork
|
7
|
+
# configure do |config|
|
8
|
+
# config[:logger] = <%= camelized_daemon_name %>.app.logger
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# every(1.hour, 'label for logger') { Feed.send_later(:refresh) }
|
12
|
+
# every(10.seconds, 'label for logger') { run_something }
|
13
|
+
# every(1.day, 'label for logger', at: '01:30') { run_daily_processing }
|
14
|
+
# end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# A dummy SubprocessLoop that logs Hello World every 10 seconds, offseted by 5 seconds
|
2
|
+
class FooSubprocessLoop < Archfiend::SubprocessLoop
|
3
|
+
def run
|
4
|
+
sleep 5
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def iterate
|
9
|
+
msg = "Hello World from #{self.class.name}"
|
10
|
+
logger.info(msg)
|
11
|
+
sleep 10
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('boot', __dir__)
|
2
|
+
require 'archfiend'
|
3
|
+
|
4
|
+
# Your daemon namespace
|
5
|
+
module <%= camelized_daemon_name %>
|
6
|
+
extend Archfiend::Utilities
|
7
|
+
|
8
|
+
# Main daemon application class
|
9
|
+
class Application < Archfiend::Application
|
10
|
+
# @return [Module] A module containing Archfiend::Utilities
|
11
|
+
def utils
|
12
|
+
::<%= camelized_daemon_name %>
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Pathname] An absolute path to the main directory of the daemon
|
17
|
+
def self.root
|
18
|
+
@root ||= Pathname.new(File.expand_path('..', __dir__))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Bundler.require(*<%= camelized_daemon_name %>.groups)
|
23
|
+
|
24
|
+
silence_warnings do
|
25
|
+
Encoding.default_external = Encoding::UTF_8
|
26
|
+
Encoding.default_internal = Encoding::UTF_8
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('application', __dir__)
|
2
|
+
|
3
|
+
daemon_opts = {
|
4
|
+
app_name: '<%= daemon_name %>',
|
5
|
+
log_dir: File.join(File.expand_path(__dir__), 'log'),
|
6
|
+
logfilename: "#{<%= camelized_daemon_name %>.app.env}.log"
|
7
|
+
}
|
8
|
+
|
9
|
+
# Daemons.daemonize closes all file descriptors and prepares the app for the fork.
|
10
|
+
# Make sure new code doesn't rely on connections / logs etc opened before this call
|
11
|
+
Daemons.daemonize(daemon_opts)
|
12
|
+
|
13
|
+
# Sets up Settings and many more
|
14
|
+
<%= camelized_daemon_name %>.app.setup
|
15
|
+
Process.setproctitle "#{Settings.app_name} (#{<%= camelized_daemon_name %>.app.env})"
|
16
|
+
|
17
|
+
# Runs the actual application
|
18
|
+
<%= camelized_daemon_name %>.app.run
|
@@ -0,0 +1,26 @@
|
|
1
|
+
default: &default
|
2
|
+
adapter: postgresql
|
3
|
+
encoding: utf8
|
4
|
+
database: <%%= ENV.fetch('DATABASE_NAME', '<%= daemon_name %>_development') %>
|
5
|
+
|
6
|
+
username: <%%= ENV.fetch('DATABASE_USERNAME', 'username') %>
|
7
|
+
password: <%%= ENV.fetch('DATABASE_PASSWORD', 'password') %>
|
8
|
+
|
9
|
+
host: <%%= ENV.fetch('DATABASE_HOST', 'localhost') %>
|
10
|
+
port: <%%= ENV.fetch('DATABASE_PORT', 5432) %>
|
11
|
+
|
12
|
+
pool: 12
|
13
|
+
reconnect: true
|
14
|
+
|
15
|
+
development:
|
16
|
+
<<: *default
|
17
|
+
|
18
|
+
test:
|
19
|
+
<<: *default
|
20
|
+
database: <%= daemon_name %>_test
|
21
|
+
|
22
|
+
staging:
|
23
|
+
<<: *default
|
24
|
+
|
25
|
+
production:
|
26
|
+
<<: *default
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,43 @@
|
|
1
|
+
ENV['APP_ENV'] = 'test'
|
2
|
+
|
3
|
+
require File.expand_path('../config/environment', __dir__)
|
4
|
+
require 'database_cleaner'
|
5
|
+
require 'support/timecop'
|
6
|
+
require 'support/factory_bot'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
# Enable flags like --only-failures and --next-failure
|
10
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
11
|
+
|
12
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
13
|
+
config.disable_monkey_patching!
|
14
|
+
|
15
|
+
config.expect_with :rspec do |c|
|
16
|
+
c.syntax = :expect
|
17
|
+
c.include_chain_clauses_in_custom_matcher_descriptions = true
|
18
|
+
end
|
19
|
+
|
20
|
+
config.mock_with :rspec do |mocks|
|
21
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
22
|
+
# a real object. This is generally recommended, and will default to
|
23
|
+
# `true` in RSpec 4.
|
24
|
+
mocks.verify_partial_doubles = true
|
25
|
+
end
|
26
|
+
|
27
|
+
config.around do |example|
|
28
|
+
ActiveRecord::Base.logger = nil
|
29
|
+
DatabaseCleaner.strategy = :truncation
|
30
|
+
DatabaseCleaner.cleaning { example.run }
|
31
|
+
end
|
32
|
+
|
33
|
+
config.include(Shoulda::Matchers::ActiveModel, type: :model)
|
34
|
+
config.include(Shoulda::Matchers::ActiveRecord, type: :model)
|
35
|
+
end
|
36
|
+
|
37
|
+
Shoulda::Matchers.configure do |config|
|
38
|
+
config.integrate do |with|
|
39
|
+
with.test_framework :rspec
|
40
|
+
with.library :active_record
|
41
|
+
with.library :active_model
|
42
|
+
end
|
43
|
+
end
|
File without changes
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Archfiend
|
2
|
+
module Generators
|
3
|
+
class Extensions
|
4
|
+
PHASES = %i[init exec].freeze
|
5
|
+
CALLBACK_TYPES = %i[before after].freeze
|
6
|
+
|
7
|
+
# @param generator_options [Archfiend::Generators::Options] Options, source of potential extensions
|
8
|
+
# @param action_context [Thor::Group] Contex of the extended action/group of actions
|
9
|
+
# @param generator_name [String] Underscore form name of the generator, ex. daemon
|
10
|
+
def initialize(generator_options, action_context, generator_name)
|
11
|
+
@generator_options = generator_options
|
12
|
+
@action_context = action_context
|
13
|
+
@generator_name = generator_name
|
14
|
+
|
15
|
+
@extensions = activate_extensions
|
16
|
+
expose_extensions
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_with_init_callbacks
|
20
|
+
run_callback(:init, :before)
|
21
|
+
|
22
|
+
yield unless skip_default_action?(:init)
|
23
|
+
|
24
|
+
run_callback(:init, :after)
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_with_exec_callbacks
|
28
|
+
run_callback(:exec, :before)
|
29
|
+
|
30
|
+
yield unless skip_default_action?(:exec)
|
31
|
+
|
32
|
+
run_callback(:exec, :after)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def skip_default_action?(phase)
|
38
|
+
generator_extensions.any? do |ge|
|
39
|
+
method_name = "skip_default_#{phase}_action?"
|
40
|
+
ge.respond_to?(method_name) && ge.public_send(method_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def run_callback(phase, callback_type)
|
45
|
+
fail(ArgumentError, "Unsupported phase #{phase}") unless PHASES.include?(phase)
|
46
|
+
fail(ArgumentError, "Unsupported callback_type #{callback_type}") unless CALLBACK_TYPES.include?(callback_type)
|
47
|
+
|
48
|
+
callback_action = [callback_type, phase].join('_')
|
49
|
+
generator_extensions.select { |ge| ge.respond_to?(callback_action) }.each { |ge| ge.public_send(callback_action) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_before_create_extensions
|
53
|
+
generator_extensions.select { |ge| ge.respond_to?(:before_create) }.each(&:before_create)
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_after_create_extensions
|
57
|
+
generator_extensions.select { |ge| ge.respond_to?(:after_create) }.each(&:after_create)
|
58
|
+
end
|
59
|
+
|
60
|
+
def generator_extensions
|
61
|
+
@generator_extensions ||= @extensions.map do |extension_module|
|
62
|
+
next unless extension_module.const_defined?(generator_extensions_class_name)
|
63
|
+
extension_klass = extension_module.const_get(generator_extensions_class_name)
|
64
|
+
next if extension_klass.respond_to?(:target_generator_name) && extension_klass.target_generator_name != @generator_name
|
65
|
+
|
66
|
+
extension_klass.new(@action_context, @generator_options)
|
67
|
+
end.compact
|
68
|
+
end
|
69
|
+
|
70
|
+
def generator_extensions_class_name
|
71
|
+
"Generators::#{@generator_name.camelize}Extensions" # example: Generators::DaemonExtensions
|
72
|
+
end
|
73
|
+
|
74
|
+
def expose_extensions # rubocop:disable Metrics/AbcSize
|
75
|
+
generator_extensions.each do |generator_extension|
|
76
|
+
exposed_name = if generator_extension.respond_to?(:exposed_name)
|
77
|
+
generator_extension.exposed_name
|
78
|
+
else
|
79
|
+
generator_extension.class.name.split(':').first.underscore
|
80
|
+
end
|
81
|
+
|
82
|
+
if @action_context.respond_to?(exposed_name)
|
83
|
+
puts "Extension's exposed_name #{exposed_name.inspect} conflicts with existing method defined in #{@action_context.method(exposed_name).source_location}."
|
84
|
+
puts "Please define a method #{generator_extension.class.name}#exposed_name with some other value."
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
|
88
|
+
@action_context.instance_variable_set("@#{exposed_name}", generator_extension)
|
89
|
+
@action_context.class.attr_reader(exposed_name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def activate_extensions # rubocop:disable Metrics/AbcSize
|
94
|
+
extensions = []
|
95
|
+
|
96
|
+
@generator_options.extensions.each do |extension|
|
97
|
+
begin
|
98
|
+
Kernel.gem(extension)
|
99
|
+
Kernel.require(extension)
|
100
|
+
rescue LoadError => e
|
101
|
+
puts "Unable to load requested extension gem #{extension}, aborting"
|
102
|
+
puts e.inspect.to_s
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
module_name = extension.camelize
|
106
|
+
if Object.const_defined?(module_name)
|
107
|
+
extensions << Object.const_get(module_name)
|
108
|
+
puts "Activated extension gem #{module_name}"
|
109
|
+
else
|
110
|
+
puts "Failed to recognize extension module #{module_name} in gem #{extension}, aborting"
|
111
|
+
exit 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
extensions
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|