lepus 0.0.1.beta2

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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/specs.yml +44 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +35 -0
  6. data/.tool-versions +1 -0
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +120 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +213 -0
  12. data/Rakefile +4 -0
  13. data/bin/console +9 -0
  14. data/bin/setup +7 -0
  15. data/docker-compose.yml +8 -0
  16. data/exec/lepus +9 -0
  17. data/gemfiles/rails52.gemfile +5 -0
  18. data/gemfiles/rails52.gemfile.lock +242 -0
  19. data/gemfiles/rails61.gemfile +5 -0
  20. data/gemfiles/rails61.gemfile.lock +260 -0
  21. data/lepus.gemspec +53 -0
  22. data/lib/lepus/app_executor.rb +19 -0
  23. data/lib/lepus/cli.rb +27 -0
  24. data/lib/lepus/configuration.rb +90 -0
  25. data/lib/lepus/consumer.rb +177 -0
  26. data/lib/lepus/consumer_config.rb +149 -0
  27. data/lib/lepus/consumer_wrapper.rb +46 -0
  28. data/lib/lepus/lifecycle_hooks.rb +49 -0
  29. data/lib/lepus/message.rb +37 -0
  30. data/lib/lepus/middleware.rb +18 -0
  31. data/lib/lepus/middlewares/honeybadger.rb +23 -0
  32. data/lib/lepus/middlewares/json.rb +35 -0
  33. data/lib/lepus/middlewares/max_retry.rb +57 -0
  34. data/lib/lepus/primitive/string.rb +55 -0
  35. data/lib/lepus/process.rb +136 -0
  36. data/lib/lepus/process_registry.rb +37 -0
  37. data/lib/lepus/processes/base.rb +50 -0
  38. data/lib/lepus/processes/callbacks.rb +72 -0
  39. data/lib/lepus/processes/consumer.rb +113 -0
  40. data/lib/lepus/processes/interruptible.rb +38 -0
  41. data/lib/lepus/processes/procline.rb +11 -0
  42. data/lib/lepus/processes/registrable.rb +67 -0
  43. data/lib/lepus/processes/runnable.rb +102 -0
  44. data/lib/lepus/processes/supervised.rb +44 -0
  45. data/lib/lepus/processes.rb +6 -0
  46. data/lib/lepus/producer.rb +42 -0
  47. data/lib/lepus/rails/log_subscriber.rb +120 -0
  48. data/lib/lepus/rails/railtie.rb +31 -0
  49. data/lib/lepus/rails.rb +7 -0
  50. data/lib/lepus/supervisor/config.rb +45 -0
  51. data/lib/lepus/supervisor/maintenance.rb +35 -0
  52. data/lib/lepus/supervisor/pidfile.rb +61 -0
  53. data/lib/lepus/supervisor/pidfiled.rb +29 -0
  54. data/lib/lepus/supervisor/signals.rb +71 -0
  55. data/lib/lepus/supervisor.rb +204 -0
  56. data/lib/lepus/timer.rb +29 -0
  57. data/lib/lepus/version.rb +5 -0
  58. data/lib/lepus.rb +95 -0
  59. data/lib/puma/plugin/lepus.rb +74 -0
  60. metadata +290 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aecb8b8247c618b92702f4d38ddd12662dbb5e8d4b1c18782796ab7caf790c6b
4
+ data.tar.gz: 3da8d288fad18c9174db8e18c989e1f6e861d0ced3116e6ea20c6ce892d80f53
5
+ SHA512:
6
+ metadata.gz: eb553b2b3b980b9d64c5a04f9c4c56634003e7f3e850b17a2437343ebc9834019d5778673e891ce2f0ec8abec02b2f54155fbf5b3c9766a80348497139a47995
7
+ data.tar.gz: cbea89d36fffc882720d0f148549c39a5f0d7211ff38035218e387549c14d2a716012ddafa616599c4e819a6346ccb5b9ed794c8b73369aaf5e223af6dc0449e
@@ -0,0 +1,44 @@
1
+ name: Specs
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: ['2.7', '3.0']
16
+ gemfile:
17
+ - Gemfile
18
+ - gemfiles/rails61.gemfile
19
+ name: ${{ matrix.ruby }}-${{ matrix.gemfile }}
20
+ env:
21
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
22
+ services:
23
+ rabbitmq:
24
+ image: rabbitmq:3-management
25
+ options: >-
26
+ --health-cmd "rabbitmq-diagnostics ping"
27
+ --health-interval 10s
28
+ --health-timeout 5s
29
+ --health-retries 5
30
+ ports:
31
+ - 5672:5672
32
+ - 15672:15672
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby }}
38
+ bundler-cache: true
39
+ - name: Install dependencies
40
+ run: bundle install
41
+ - name: Run tests
42
+ run: bundle exec rspec
43
+ env:
44
+ RABBITMQ_URL: amqp://localhost:5672
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.env
3
+ /.rspec_status
4
+ /.yardoc
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.gem
12
+ /app/consumers/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,35 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
5
+ require:
6
+ - rubocop-performance
7
+ - rubocop-rspec
8
+ - standard/cop/block_single_line_braces
9
+
10
+ inherit_gem:
11
+ standard: config/base.yml
12
+
13
+ AllCops:
14
+ TargetRubyVersion: 2.5
15
+ SuggestExtensions: false
16
+ Exclude:
17
+ - "db/**/*"
18
+ - "tmp/**/*"
19
+ - "vendor/**/*"
20
+ NewCops: enable
21
+
22
+ RSpec/MultipleExpectations:
23
+ Enabled: false
24
+
25
+ RSpec/ExampleLength:
26
+ Enabled: false
27
+
28
+ RSpec/MultipleMemoizedHelpers:
29
+ Enabled: false
30
+
31
+ RSpec/MessageSpies:
32
+ Enabled: false
33
+
34
+ RSpec/StubbedMock:
35
+ Enabled: false
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.7.8
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 0.0.1 - 2024-08-01
8
+ The first release of the gem
9
+ * Added: Initial implementation with support to Sidekiq and Faktory backends
10
+ * UniqueJob middleware to avoid duplicated jobs
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in lepus.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,120 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lepus (0.0.1.beta2)
5
+ bunny
6
+ concurrent-ruby
7
+ multi_json
8
+ thor
9
+ zeitwerk
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ addressable (2.8.7)
15
+ public_suffix (>= 2.0.2, < 7.0)
16
+ amq-protocol (2.3.2)
17
+ ast (2.4.2)
18
+ bigdecimal (3.1.8)
19
+ bunny (2.23.0)
20
+ amq-protocol (~> 2.3, >= 2.3.1)
21
+ sorted_set (~> 1, >= 1.0.2)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.3.5)
24
+ crack (1.0.0)
25
+ bigdecimal
26
+ rexml
27
+ diff-lcs (1.5.1)
28
+ dotenv (2.8.1)
29
+ hashdiff (1.1.1)
30
+ json (2.7.5)
31
+ language_server-protocol (3.17.0.3)
32
+ lint_roller (1.1.0)
33
+ method_source (1.1.0)
34
+ multi_json (1.15.0)
35
+ parallel (1.26.3)
36
+ parser (3.3.5.0)
37
+ ast (~> 2.4.1)
38
+ racc
39
+ pry (0.14.2)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
42
+ public_suffix (5.1.1)
43
+ racc (1.8.1)
44
+ rainbow (3.1.1)
45
+ rbtree (0.4.6)
46
+ regexp_parser (2.9.2)
47
+ rexml (3.3.9)
48
+ rspec (3.13.0)
49
+ rspec-core (~> 3.13.0)
50
+ rspec-expectations (~> 3.13.0)
51
+ rspec-mocks (~> 3.13.0)
52
+ rspec-core (3.13.2)
53
+ rspec-support (~> 3.13.0)
54
+ rspec-expectations (3.13.3)
55
+ diff-lcs (>= 1.2.0, < 2.0)
56
+ rspec-support (~> 3.13.0)
57
+ rspec-mocks (3.13.2)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.13.0)
60
+ rspec-support (3.13.1)
61
+ rubocop (1.64.1)
62
+ json (~> 2.3)
63
+ language_server-protocol (>= 3.17.0)
64
+ parallel (~> 1.10)
65
+ parser (>= 3.3.0.2)
66
+ rainbow (>= 2.2.2, < 4.0)
67
+ regexp_parser (>= 1.8, < 3.0)
68
+ rexml (>= 3.2.5, < 4.0)
69
+ rubocop-ast (>= 1.31.1, < 2.0)
70
+ ruby-progressbar (~> 1.7)
71
+ unicode-display_width (>= 2.4.0, < 3.0)
72
+ rubocop-ast (1.33.0)
73
+ parser (>= 3.3.1.0)
74
+ rubocop-performance (1.21.1)
75
+ rubocop (>= 1.48.1, < 2.0)
76
+ rubocop-ast (>= 1.31.1, < 2.0)
77
+ rubocop-rspec (3.2.0)
78
+ rubocop (~> 1.61)
79
+ ruby-progressbar (1.13.0)
80
+ set (1.0.4)
81
+ sorted_set (1.0.3)
82
+ rbtree
83
+ set (~> 1.0)
84
+ standard (1.37.0)
85
+ language_server-protocol (~> 3.17.0.2)
86
+ lint_roller (~> 1.0)
87
+ rubocop (~> 1.64.0)
88
+ standard-custom (~> 1.0.0)
89
+ standard-performance (~> 1.4)
90
+ standard-custom (1.0.2)
91
+ lint_roller (~> 1.0)
92
+ rubocop (~> 1.50)
93
+ standard-performance (1.4.0)
94
+ lint_roller (~> 1.1)
95
+ rubocop-performance (~> 1.21.0)
96
+ thor (1.3.2)
97
+ unicode-display_width (2.6.0)
98
+ webmock (3.24.0)
99
+ addressable (>= 2.8.0)
100
+ crack (>= 0.3.2)
101
+ hashdiff (>= 0.4.0, < 2.0.0)
102
+ zeitwerk (2.6.18)
103
+
104
+ PLATFORMS
105
+ ruby
106
+ x86_64-linux
107
+
108
+ DEPENDENCIES
109
+ dotenv
110
+ lepus!
111
+ pry
112
+ rspec
113
+ rubocop
114
+ rubocop-performance
115
+ rubocop-rspec
116
+ standard
117
+ webmock
118
+
119
+ BUNDLED WITH
120
+ 2.3.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Marcos G. Zimmermann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # Lepus
2
+
3
+
4
+ Lepus is a simple and lightweight Ruby library to help you to consume and produce messages to [RabbitMQ](https://www.rabbitmq.com/) using the [Bunny](https://github.com/ruby-amqp/bunny) gem. It's similar to the Sidekiq, Faktory, ActiveJob, SolidQueue, and other libraries, but using RabbitMQ as the message broker.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'lepus'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```bash
17
+ bundle install
18
+ ```
19
+
20
+ Or install it yourself as:
21
+
22
+ ```bash
23
+ gem install lepus
24
+ ```
25
+
26
+ ## Usage
27
+
28
+
29
+ ## Configuration
30
+
31
+ You can configure the Lepus using the `Lepus.configure` method. The configuration options are:
32
+
33
+ - `rabbitmq_url`: The RabbitMQ host. Default: to `RABBITMQ_URL` environment variable or `amqp://guest:guest@localhost:5672`.
34
+ - `connection_name`: The connection name. Default: `Lepus`.
35
+ - `recovery_attempts`: The number of attempts to recover the connection. Nil means infinite. Default: `10`.
36
+ - `recover_from_connection_close`: If the connection should be recovered when it's closed. Default: `true`.
37
+ - `app_executor`: The [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) used to wrap asynchronous operations. Only available if you are using Rails. Default: `nil`.
38
+ - `on_thread_error`: The block to be executed when an error occurs on the thread. Default: `nil`.
39
+ - `process_heartbeat_interval`: The interval in seconds between heartbeats. Default is `60 seconds`.
40
+ - `process_alive_threshold`: the threshold in seconds to consider a process alive. Default is `5 minutes`.
41
+
42
+
43
+ ```ruby
44
+ Lepus.configure do |config|
45
+ config.connection_name = 'MyApp'
46
+ config.rabbitmq_url = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
47
+ end
48
+ ```
49
+
50
+ ## Defining a Consumer
51
+
52
+ To define a consumer, you need to create a class inheriting from `Lepus::Consumer` and implement the `perform` method. The `perform` method will be called when a message is received. Use the `configure` method to set the queue name, exchange name, and other options.
53
+
54
+ The example below defines a consumer with required settings:
55
+
56
+ ```ruby
57
+ class MyConsumer < Lepus::Consumer
58
+ configure(
59
+ queue: "queue_name",
60
+ exchange: "exchange_name",
61
+ routing_key: %w[routing_key1 routing_key2],
62
+ )
63
+
64
+ def perform(message)
65
+ puts "delivery_info: #{message.delivery_info}"
66
+ puts "metadata: #{message.metadata}"
67
+ puts "payload: #{message.payload}"
68
+
69
+ ack!
70
+ end
71
+ end
72
+ ```
73
+
74
+ ### Consumer Configuration
75
+
76
+ The `configure` method accepts the following options:
77
+
78
+ - **\*** `queue`: . The queue name or a Hash with the queue options. Default: `nil`.
79
+ - **\*** `exchange`: The exchange name or a Hash with the exchange options. Default: `nil`.
80
+ - `routing_key`: One or more routing keys. Default: `nil`.
81
+ - `bind`: The binding options. Default: `nil`.
82
+ - `retry_queue`: Boolean or a Hash to configure the retry queue. Default: `false`.
83
+ - `error_queue`: Boolean or a Hash to configure the error queue. Default: `false`.
84
+
85
+ Options marked with `*` are required.
86
+
87
+ You can pass a more descriptive configuration using a Hash with custom options for each part of message broker:
88
+
89
+ ```ruby
90
+ class MyConsumer < Lepus::Consumer
91
+ configure(
92
+ queue: {
93
+ name: "queue_name",
94
+ durable: true,
95
+ # You can set any other exchange options here
96
+ # arguments: { 'x-message-ttl' => 60_000 }
97
+ },
98
+ exchange: {
99
+ name: "exchange_name",
100
+ type: :direct, # You may use :direct, :fanout, :topic, or :headers
101
+ durable: true,
102
+ # You can set any other exchange options here
103
+ # arguments: { 'x-message-ttl' => 60_000 }
104
+ },
105
+ bind: {
106
+ routing_key: %w[routing_key1 routing_key2]
107
+ },
108
+ retry_queue: { # As shortcut, you can just pass `true` to create a retry queue with default options below.
109
+ name: "queue_name.retry",
110
+ durable: true,
111
+ delay: 5000,
112
+ },
113
+ error_queue: { # As shortcut, you can just pass `true` to create an error queue with default options below.
114
+ name: "queue_name.error",
115
+ durable: true,
116
+ }
117
+ )
118
+ # ...
119
+ end
120
+ ```
121
+
122
+ By declaring the `retry_queue` option, it will automatically create a queue named `queue_name.retry` and use the arguments `x-dead-letter-exchange` and `x-dead-letter-routing-key` to route rejected messages to it. When routed to the retry queue, messages will wait there for the number of milliseconds specified in `delay`, after which they will be redelivered to the original queue.
123
+ **Note that this will not automatically catch unhandled errors. You still have to catch any errors yourself and reject your message manually for the retry mechanism to work.**
124
+
125
+ It may result in a infinite loop if the message is always rejected. To avoid this, you can use the `error_queue` option to route the message to an `queue_name.error` queue after a number of attempts by using the `MaxRetry` middleware covered in the next section.
126
+
127
+ Refer to the [Dead Letter Exchanges](https://www.rabbitmq.com/docs/dlx) documentation for more information about retry.
128
+
129
+ ### Middlewares
130
+
131
+ Consumers can use middlewares for recurring tasks like logging, error handling, parsing, and others. It comes with a few built-in middlewares:
132
+
133
+ * `:max_retry`: Rejects the message and routes it to the error queue after a number of attempts.
134
+ * `:json`: Parses the message payload as JSON.
135
+
136
+ You can use the `use` method to add middlewares to the consumer:
137
+
138
+ ```ruby
139
+ class MyConsumer < Lepus::Consumer
140
+ use(
141
+ :max_retry,
142
+ retries: 6,
143
+ error_queue: "queue_name.error" # The queue to route the message after the number of retries
144
+ )
145
+
146
+ use(
147
+ :json,
148
+ symbolize_keys: true,
149
+ on_error: proc { :nack } # The default is :reject on parsing error
150
+ )
151
+
152
+ def perform(message)
153
+ puts message.payload[:name]
154
+ ack!
155
+ end
156
+ end
157
+ ```
158
+
159
+ You can also create your own middlewares, just create subclasses of `Lepus::Middleware` and implement the `call` method:
160
+
161
+ ```ruby
162
+ class MyMiddleware < Lepus::Middleware
163
+ def initialize(**options)
164
+ @options = options
165
+ end
166
+
167
+ def call(message, app
168
+ # Do something before calling the next middleware
169
+ app.call(message)
170
+ end
171
+ end
172
+ ```
173
+
174
+ ## Starting the Consumer Process
175
+
176
+ To start the consumer, can use the `lepus` CLI:
177
+
178
+ ```bash
179
+ bundle exec lepus
180
+ ```
181
+
182
+ You can pass one or more consumers to the `lepus` CLI:
183
+
184
+ ```bash
185
+ bundle exec lepus start MyConsumer1 MyConsumer2 --debug
186
+ ```
187
+
188
+ Each consumer will run in a separate process, and a supervisor will monitor them. If a consumer crashes, the supervisor will restart it.
189
+
190
+ ### Puma Plugin
191
+
192
+ We provide a Puma plugin if you want to run the Lepus's supervisor together with Puma and have Puma monitor and manage it. You just need to add
193
+
194
+ ```ruby
195
+ plugin :lepus
196
+ ```
197
+
198
+ **Note**: The Puma plugin is only available if you are using Puma 6.x or higher.
199
+
200
+ ## Development
201
+
202
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
203
+
204
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
205
+
206
+ ## Contributing
207
+
208
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marcosgz/lepus.
209
+
210
+
211
+ ## License
212
+
213
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dotenv/load"
6
+ require "pry"
7
+ require "lepus"
8
+
9
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+ find gemfiles -type f \( -iname "*.gemfile" ! -iname "*.lock" \) -exec bundle install --gemfile {} \;
@@ -0,0 +1,8 @@
1
+ services:
2
+ rabbitmq:
3
+ image: rabbitmq
4
+ ports:
5
+ - 5672:5672
6
+ - 15672:15672
7
+ command: >
8
+ sh -c "rabbitmq-plugins enable rabbitmq_management && rabbitmq-server"
data/exec/lepus ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+
6
+ require "lepus"
7
+ require "lepus/cli"
8
+
9
+ Lepus::CLI.start
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: ".."
4
+
5
+ gem "rails", "~> 5.2", ">= 5.2.8.1"