archfiend 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|