jobly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ad67b7e96a1ee27e3034c812d05026f922b72585d70d0c8085f9173f8e70d04
4
+ data.tar.gz: 9614bb05c9da5bf17a9e2d25e3aafdd859bf811828f71c6a511557dc926985ae
5
+ SHA512:
6
+ metadata.gz: c891c3bbff7c1c37180fdc186b22f63a50730f978125546e48a2231ebeab630bb27312016c3d47fa39afcc48dd79bb0e59bb96c1abafebbc16804f77eec8da42
7
+ data.tar.gz: f021ecf23d579f53fc713b4657ca2613b429da6e7ff2e503e21bf9edb3d4b9f18bf48362349deafd0e1fab36324d51f029ea2d4e9f8dc5d38b43912ce63dce42
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ Jobly
2
+ ==================================================
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/jobly.svg)](https://badge.fury.io/rb/jobly)
5
+ [![Build Status](https://travis-ci.com/DannyBen/jobly.svg?branch=master)](https://travis-ci.com/DannyBen/jobly)
6
+
7
+ ---
8
+
9
+ Job server toolbelt with API, CLI, Web UI and a Sidekiq heart.
10
+
11
+ ---
12
+
13
+ Installation
14
+ --------------------------------------------------
15
+
16
+ $ gem install jobly
17
+
18
+
19
+ What's in the Box
20
+ --------------------------------------------------
21
+
22
+ Jobly is a command line utility that wraps several goodies around the
23
+ sidekiq backgronud jobs system. It includes the following components:
24
+
25
+ - **Command line interface** - for starting the server, starting the worker,
26
+ and for running jobs.
27
+ - **Web API** - for executing jobs.
28
+ - **Web Dashboard** - including job progress and status.
29
+
30
+ Getting Started
31
+ --------------------------------------------------
32
+
33
+ TODO
34
+
35
+
36
+ Usage
37
+ --------------------------------------------------
38
+
39
+ ### Server
40
+
41
+ To start the server run `jobly server` and open <http://localhost:3000/>
42
+ in your browser.
43
+
44
+ This will start a webserver with two primary entrypoints:
45
+
46
+ - `/` (root) - a dashboard for your background job processes.
47
+ - `/do/:job?param=value` - an API for executing jobs
48
+
49
+
50
+ ### Worker
51
+
52
+ To start processing jobs, run `jobly worker`
53
+
54
+
55
+ ### Running jobs from the command line
56
+
57
+ There are three ways to run a job from the command line:
58
+
59
+ Run the job locally, without going through any of the background job
60
+ processing chain:
61
+
62
+ ```ruby
63
+ jobly run JobName param:value
64
+ ```
65
+
66
+ Run the job locally, but wait for a worker to process it.
67
+
68
+ ```ruby
69
+ jobly run --later JobName param:value
70
+ ```
71
+
72
+ Send a job through the API (either localhost or remote).
73
+
74
+ ```ruby
75
+ jobly send JobName param:value
76
+ ```
data/bin/jobly ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require 'colsole'
3
+ require 'jobly'
4
+ require 'jobly/boot'
5
+
6
+ include Colsole
7
+
8
+ router = Jobly::CLI.router
9
+
10
+ begin
11
+ exit router.run ARGV
12
+ rescue => e
13
+ puts e.backtrace.reverse if ENV['DEBUG']
14
+ say! "!txtred!#{e.class}: #{e.message}"
15
+ exit 1
16
+ end
data/lib/jobly.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'requires'
2
+ require 'byebug' if ENV['BYEBUG']
3
+
4
+ requires 'jobly/refinements'
5
+ require 'jobly/exceptions'
6
+ require 'jobly/sidekiq'
7
+ require 'jobly/module_functions'
8
+ require 'jobly/cli'
9
+ require 'jobly/job'
10
+ require 'jobly/jobs'
11
+ require 'jobly/server'
data/lib/jobly/boot.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'jobly'
2
+
3
+ Jobly.load_custom_config
4
+ Jobly::Jobs.load_all
5
+ Jobly::SidekiqBoot.configure
data/lib/jobly/cli.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'mister_bin'
2
+ requires 'commands/base', 'commands'
3
+
4
+ module Jobly
5
+
6
+ # Command line interface router. This is called by bin/jobly.
7
+ class CLI
8
+ def self.router
9
+ router = MisterBin::Runner.new version: VERSION,
10
+ header: "Jobly",
11
+ footer: "Run !txtpur!jobly COMMAND --help!txtrst! for more information"
12
+
13
+ router.route 'server', to: Commands::ServerCmd
14
+ router.route 'worker', to: Commands::WorkerCmd
15
+ router.route 'send', to: Commands::SendCmd
16
+ router.route 'run', to: Commands::RunCmd
17
+ router.route 'config', to: Commands::ConfigCmd
18
+
19
+ router
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,11 @@
1
+ require 'mister_bin'
2
+ require 'colsole'
3
+ require 'lp'
4
+
5
+ module Jobly
6
+ module Commands
7
+ class Base < MisterBin::Command
8
+ include Colsole
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ require 'pathname'
2
+
3
+ module Jobly
4
+ module Commands
5
+ class ConfigCmd < Base
6
+ summary "Show configuration options"
7
+ usage "jobly config"
8
+ usage "jobly config (-h|--help)"
9
+
10
+ def run
11
+ line "custom config file", short_config_path, Jobly.custom_config?
12
+ Jobly.options.each do |key, value|
13
+ if key.to_s.end_with? '_path'
14
+ line key, value, !Dir.exist?(value)
15
+ else
16
+ line key, value
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def short_config_path
24
+ Jobly.config_file.sub "#{Dir.pwd}", '.'
25
+ end
26
+
27
+ def line(key, value, attention=false)
28
+ color = attention ? '!txtred!' : '!txtgrn!'
29
+ say "#{key.to_s.rjust 20} #{color}#{value.to_s.strip}"
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ module Jobly
2
+ module Commands
3
+ class RunCmd < Base
4
+ using ArgumentConverters
5
+ using ConvertToTyped
6
+
7
+ summary "Run a job locally"
8
+ usage "jobly run [--later] JOB [PARAMS...]"
9
+ usage "jobly run (-h|--help)"
10
+ option "-l, --later", "Schedule the job to be executed later by a worker instead of running it immediately"
11
+ param "JOB", "Job name"
12
+ param "PARAMS", "Parameters to pass to the job as key:value"
13
+ example "jobly run Greet name:Bob"
14
+ example "jobly run --later Greet name:Bob"
15
+ example "jobly run Deploy env:stage branch:master"
16
+
17
+ def run
18
+ job = args['JOB']
19
+ params = args['PARAMS'].to_params.convert_to_typed
20
+ job_class = Jobs.get_class! job
21
+
22
+ if args['--later']
23
+ say "!txtgrn!Scheduling"
24
+ if params.empty?
25
+ job_class.perform_async
26
+ else
27
+ job_class.perform_async params
28
+ end
29
+
30
+ else
31
+ say "!txtgrn!Running"
32
+ job_class.new.perform params
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require 'http'
2
+
3
+ module Jobly
4
+ module Commands
5
+ class SendCmd < Base
6
+ using ArgumentConverters
7
+
8
+ summary "Send a job to the API"
9
+ usage "jobly send JOB [PARAMS...]"
10
+ usage "jobly send (-h|--help)"
11
+ param "JOB", "Job name"
12
+ param "PARAMS", "Parameters to pass to the job as key:value"
13
+ example "jobly send Greet name:Bob"
14
+ example "jobly send Deploy env:stage branch:master"
15
+
16
+ def run
17
+ job = args['JOB']
18
+ params = args['PARAMS'].to_params
19
+ url = "#{Jobly.api_base}/#{job}"
20
+
21
+ response = if params.empty?
22
+ HTTP.get url
23
+ else
24
+ HTTP.get url, params: params
25
+ end
26
+
27
+ if response.code != 200
28
+ say "!txtred!#{response.code}"
29
+ else
30
+ say "!txtgrn!200 OK"
31
+ end
32
+
33
+ lp response.parse
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ module Jobly
2
+ module Commands
3
+ class ServerCmd < Base
4
+ summary "Start the server"
5
+ usage "jobly server [--port NUMBER]"
6
+ usage "jobly server (-h|--help)"
7
+ option "-p, --port NUMBER", "Set the port number [default: 3000]"
8
+
9
+ def run
10
+ port = args['--port']
11
+ say "Starting server"
12
+ exec "rackup --env #{Jobly.environment} --port #{port} --host 0.0.0.0 #{rackup_file}"
13
+ end
14
+
15
+ private
16
+
17
+ def rackup_file
18
+ @rackup_file ||= File.expand_path '../config.ru', __dir__
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Jobly
2
+ module Commands
3
+ class WorkerCmd < Base
4
+ summary "Start a job worker"
5
+ usage "jobly worker [--concurrency COUNT]"
6
+ usage "jobly worker (-h|--help)"
7
+ option "-c, --concurrency COUNT", "Number of parallel jobs [default: 4]"
8
+
9
+ def run
10
+ concurrency = args['--concurrency']
11
+ say "Starting sidekiq"
12
+ exec "sidekiq --environment #{Jobly.environment} --concurrency #{concurrency} --require #{boot_file}"
13
+ end
14
+
15
+ private
16
+
17
+ def boot_file
18
+ @boot_file ||= File.expand_path '../boot.rb', __dir__
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ require 'puma'
2
+ require 'jobly'
3
+ require 'jobly/boot'
4
+
5
+ mounts = {
6
+ '/' => Sidekiq::Web,
7
+ '/do' => Jobly::Server
8
+ }
9
+
10
+ run Rack::URLMap.new mounts
@@ -0,0 +1,4 @@
1
+ module Jobly
2
+ class Error < StandardError; end
3
+ class JobNotFound < Error; end
4
+ end
data/lib/jobly/job.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Jobly
2
+ class Job
3
+ include Sidekiq::Worker
4
+ include Sidekiq::Status::Worker
5
+ sidekiq_options retry: 5
6
+
7
+ def perform(params={})
8
+ params = params.to_h.transform_keys(&:to_sym)
9
+ params.empty? ? execute : execute(params)
10
+ end
11
+
12
+ def execute(params={})
13
+ raise NotImplementedError
14
+ end
15
+ end
16
+ end
data/lib/jobly/jobs.rb ADDED
@@ -0,0 +1,27 @@
1
+ module Jobly
2
+ module Jobs
3
+ def self.get_class(job)
4
+ Object.const_get namespaced_job(job) rescue nil
5
+ end
6
+
7
+ def self.get_class!(job)
8
+ Object.const_get namespaced_job(job)
9
+ rescue NameError
10
+ raise JobNotFound, job
11
+ end
12
+
13
+ def self.load_all
14
+ Dir["#{Jobly.full_app_path}/**/*.rb"].each { |file| require file }
15
+ Dir["#{Jobly.full_jobs_path}/**/*.rb"].each { |file| require file }
16
+ end
17
+
18
+ def self.namespaced_job(job)
19
+ if Jobly.jobs_namespace
20
+ "#{Jobly.jobs_namespace}::#{job}"
21
+ else
22
+ job
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,65 @@
1
+ module Jobly
2
+ def self.configure
3
+ yield self
4
+ end
5
+
6
+ def self.load_custom_config
7
+ require config_file if File.exist? config_file
8
+ end
9
+
10
+ def self.default_options
11
+ {
12
+ environment: ENV['JOBLY_ENVIRONMENT'] || 'development',
13
+ api_base: ENV['JOBLY_API_URL'] || 'http://localhost:3000/do',
14
+ app_path: ENV['JOBLY_APP_PATH'] || 'app',
15
+ jobs_path: ENV['JOBLY_JOBS_PATH'] || "jobs",
16
+ config_path: ENV['JOBLY_CONFIG_PATH'] || "config",
17
+ redis_url: ENV['JOBLY_REDIS_URL'] || "redis://localhost:6379/0",
18
+ status_expiration: 30,
19
+ jobs_namespace: nil,
20
+ logger: nil,
21
+ }
22
+ end
23
+
24
+ def self.method_missing(method, args=nil, &_block)
25
+ key = method.to_s
26
+ assign = key[-1] == "="
27
+ key = key.chomp('=') if assign
28
+ key = key.to_sym
29
+
30
+ if options.has_key? key
31
+ assign ? options[key] = args : options[key]
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def self.respond_to_missing?(method, include_private=false)
38
+ key = method.to_s.chomp('=').to_sym
39
+ options.has_key?(key) ? true : super
40
+ end
41
+
42
+ def self.options
43
+ @options ||= default_options.dup
44
+ end
45
+
46
+ def self.full_app_path
47
+ File.expand_path app_path
48
+ end
49
+
50
+ def self.full_jobs_path
51
+ File.expand_path jobs_path
52
+ end
53
+
54
+ def self.full_config_path
55
+ File.expand_path config_path
56
+ end
57
+
58
+ def self.config_file
59
+ File.expand_path 'jobly.rb', self.config_path
60
+ end
61
+
62
+ def self.custom_config?
63
+ File.exist? config_file
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module Jobly
2
+ module ArgumentConverters
3
+ refine Array do
4
+ def to_params
5
+ map { |pair| pair.split ':' }.to_h.transform_keys(&:to_sym)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module Jobly
2
+ module ConvertToTyped
3
+ refine Hash do
4
+ def convert_to_typed
5
+ transform_values do |element|
6
+ element.is_a?(String) ? element.convert_to_typed : element
7
+ end
8
+ end
9
+ end
10
+
11
+ refine Array do
12
+ def convert_to_typed
13
+ map do |element|
14
+ element.is_a?(String) ? element.convert_to_typed : element
15
+ end
16
+ end
17
+ end
18
+
19
+ refine String do
20
+ def convert_to_typed
21
+ Integer self rescue self
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,77 @@
1
+ require 'jobly'
2
+ require 'sinatra/base'
3
+ require 'sinatra/reloader'
4
+ require 'sinatra/custom_logger'
5
+
6
+ module Jobly
7
+
8
+ class Server < Sinatra::Application
9
+ helpers Sinatra::CustomLogger
10
+ using ConvertToTyped
11
+
12
+ configure :development, :production do
13
+ set :server, :puma
14
+ set :logger, Jobly.logger if Jobly.logger
15
+ end
16
+
17
+ configure :development do
18
+ register Sinatra::Reloader
19
+ end
20
+
21
+ before do
22
+ content_type 'application/json'
23
+ end
24
+
25
+ get '/' do
26
+ {
27
+ version: VERSION,
28
+ message: %Q["I'm gonna live till I die" - Frank Sinatra]
29
+ }.to_json
30
+ end
31
+
32
+ get '/:job' do
33
+ job = params.delete :job
34
+ add_job job, params
35
+ end
36
+
37
+ post '/:job' do
38
+ job = params.delete :job
39
+ add_job job, params
40
+ end
41
+
42
+ private
43
+
44
+ def add_job(job, args={})
45
+ job_class = Jobs.get_class job
46
+
47
+ if !job_class
48
+ response = {
49
+ status: 'error',
50
+ message: 'No such job',
51
+ job: job,
52
+ params: args
53
+ }
54
+
55
+ status 404
56
+ logger.debug "[jobly server] No such job (#{job})"
57
+ return response.to_json
58
+ end
59
+
60
+ args = args.convert_to_typed
61
+ if args.empty?
62
+ job_class.perform_async
63
+ else
64
+ job_class.perform_async args
65
+ end
66
+
67
+ response = {
68
+ status: 'received',
69
+ job: job,
70
+ params: args,
71
+ }
72
+
73
+ logger.debug "[jobly server] Job received (#{job})"
74
+ response.to_json
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ require 'sidekiq'
2
+ require 'sidekiq/web'
3
+ require 'sidekiq-status'
4
+ require 'sidekiq-status/web'
5
+
6
+ module Jobly
7
+ module SidekiqBoot
8
+ def self.configure
9
+ Sidekiq.configure_client do |config|
10
+ config.redis = { url: Jobly.redis_url }
11
+ Sidekiq::Status.configure_client_middleware config, expiration: 60 * 60
12
+ end
13
+
14
+ Sidekiq.configure_server do |config|
15
+ # :nocov:
16
+ config.redis = { url: Jobly.redis_url }
17
+
18
+ Sidekiq::Status.configure_server_middleware config, expiration: Jobly.status_expiration * 60
19
+ Sidekiq::Status.configure_client_middleware config, expiration: Jobly.status_expiration * 60
20
+ # :nocov:
21
+ end
22
+
23
+ Sidekiq::Logging.logger = Jobly.logger if Jobly.logger
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Jobly
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jobly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Danny Ben Shitrit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mister_bin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colsole
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: puma
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.12'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: requires
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sidekiq
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sidekiq-status
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sinatra
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sinatra-contrib
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: http
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '4.0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '4.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: lp
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.1'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.1'
167
+ description: Execute background jobs and build tasks on this sidekiq-based job server
168
+ email: db@dannyben.com
169
+ executables:
170
+ - jobly
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - README.md
175
+ - bin/jobly
176
+ - lib/jobly.rb
177
+ - lib/jobly/boot.rb
178
+ - lib/jobly/cli.rb
179
+ - lib/jobly/commands/base.rb
180
+ - lib/jobly/commands/config.rb
181
+ - lib/jobly/commands/run.rb
182
+ - lib/jobly/commands/send.rb
183
+ - lib/jobly/commands/server.rb
184
+ - lib/jobly/commands/worker.rb
185
+ - lib/jobly/config.ru
186
+ - lib/jobly/exceptions.rb
187
+ - lib/jobly/job.rb
188
+ - lib/jobly/jobs.rb
189
+ - lib/jobly/module_functions.rb
190
+ - lib/jobly/refinements/argument_converters.rb
191
+ - lib/jobly/refinements/convert_to_typed.rb
192
+ - lib/jobly/server.rb
193
+ - lib/jobly/sidekiq.rb
194
+ - lib/jobly/version.rb
195
+ homepage: https://github.com/dannyben/jobly
196
+ licenses:
197
+ - MIT
198
+ metadata: {}
199
+ post_install_message:
200
+ rdoc_options: []
201
+ require_paths:
202
+ - lib
203
+ required_ruby_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: 2.4.0
208
+ required_rubygems_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: '0'
213
+ requirements: []
214
+ rubygems_version: 3.0.3
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: Compact job server with API, CLI, Web UI and a Sidekiq heart
218
+ test_files: []