pg_eventstore 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +4 -0
  4. data/docs/subscriptions.md +14 -56
  5. data/exe/pg-eventstore +16 -0
  6. data/lib/pg_eventstore/callbacks.rb +16 -0
  7. data/lib/pg_eventstore/cli/commands/base_command.rb +45 -0
  8. data/lib/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rb +38 -0
  9. data/lib/pg_eventstore/cli/commands/help_command.rb +22 -0
  10. data/lib/pg_eventstore/cli/commands/start_subscriptions_command.rb +96 -0
  11. data/lib/pg_eventstore/cli/commands/stop_subscriptions_command.rb +22 -0
  12. data/lib/pg_eventstore/cli/commands.rb +6 -0
  13. data/lib/pg_eventstore/cli/exit_codes.rb +12 -0
  14. data/lib/pg_eventstore/cli/parser_options/base_options.rb +46 -0
  15. data/lib/pg_eventstore/cli/parser_options/default_options.rb +10 -0
  16. data/lib/pg_eventstore/cli/parser_options/metadata.rb +34 -0
  17. data/lib/pg_eventstore/cli/parser_options/subscription_options.rb +31 -0
  18. data/lib/pg_eventstore/cli/parser_options.rb +6 -0
  19. data/lib/pg_eventstore/cli/parsers/base_parser.rb +33 -0
  20. data/lib/pg_eventstore/cli/parsers/default_parser.rb +24 -0
  21. data/lib/pg_eventstore/cli/parsers/subscription_parser.rb +24 -0
  22. data/lib/pg_eventstore/cli/parsers.rb +5 -0
  23. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +75 -0
  24. data/lib/pg_eventstore/cli/try_unlock_subscriptions_set.rb +16 -0
  25. data/lib/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rb +67 -0
  26. data/lib/pg_eventstore/cli.rb +42 -0
  27. data/lib/pg_eventstore/extensions/options_extension.rb +53 -8
  28. data/lib/pg_eventstore/rspec/has_option_matcher.rb +38 -13
  29. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +13 -1
  30. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +1 -1
  31. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +1 -1
  32. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +3 -1
  33. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +10 -50
  34. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rb +22 -0
  35. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +1 -0
  36. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +7 -2
  37. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +44 -10
  38. data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +1 -1
  39. data/lib/pg_eventstore/utils.rb +32 -0
  40. data/lib/pg_eventstore/version.rb +1 -1
  41. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +1 -1
  42. data/lib/pg_eventstore/web/views/home/partials/events.erb +5 -5
  43. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +3 -3
  44. data/lib/pg_eventstore/web/views/layouts/application.erb +3 -3
  45. data/lib/pg_eventstore/web/views/subscriptions/index.erb +6 -6
  46. data/lib/pg_eventstore.rb +5 -2
  47. data/sig/pg_eventstore/callbacks.rbs +4 -0
  48. data/sig/pg_eventstore/cli/commands/base_command.rbs +13 -0
  49. data/sig/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rbs +14 -0
  50. data/sig/pg_eventstore/cli/commands/help_command.rbs +13 -0
  51. data/sig/pg_eventstore/cli/commands/start_subscriptions_command.rbs +28 -0
  52. data/sig/pg_eventstore/cli/commands/stop_subscriptions_command.rbs +11 -0
  53. data/sig/pg_eventstore/cli/exit_codes.rbs +8 -0
  54. data/sig/pg_eventstore/cli/parser_options/base_options.rbs +15 -0
  55. data/sig/pg_eventstore/cli/parser_options/default_options.rbs +8 -0
  56. data/sig/pg_eventstore/cli/parser_options/metadata.rbs +11 -0
  57. data/sig/pg_eventstore/cli/parser_options/subscription_options.rbs +9 -0
  58. data/sig/pg_eventstore/cli/parsers/base_parser.rbs +18 -0
  59. data/sig/pg_eventstore/cli/parsers/default_parser.rbs +9 -0
  60. data/sig/pg_eventstore/cli/parsers/subscription_parser.rbs +9 -0
  61. data/sig/pg_eventstore/cli/try_to_delete_subscriptions_set.rbs +24 -0
  62. data/sig/pg_eventstore/cli/try_unlock_subscriptions_set.rbs +7 -0
  63. data/sig/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rbs +26 -0
  64. data/sig/pg_eventstore/cli.rbs +10 -0
  65. data/sig/pg_eventstore/extensions/options_extension.rbs +18 -1
  66. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rbs +8 -0
  67. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rbs +7 -1
  68. data/sig/pg_eventstore/subscriptions/queries/subscription_command_queries.rbs +1 -1
  69. data/sig/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rbs +1 -1
  70. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +10 -11
  71. data/sig/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rbs +11 -0
  72. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +1 -1
  73. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +13 -1
  74. data/sig/pg_eventstore/utils.rbs +2 -0
  75. data/sig/pg_eventstore.rbs +2 -8
  76. metadata +44 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec41b398e1f38d66e73967447ea4f12896ed2cf57eb0dbad8b71acf4257f9cd4
4
- data.tar.gz: ce571ef411adeeb18b7bae463cac339a053d96f1b36e3de0b1023b8551b03ac5
3
+ metadata.gz: 337db5e3df424d6d72b3e08367a730af7c77ebfd907d38b309309e30a563e81e
4
+ data.tar.gz: 71c36ea38c4d756adea73939ab15e75a2b4500d17768416e0f37013160f0b696
5
5
  SHA512:
6
- metadata.gz: 9273a4efb9855d219b1a92b5d9081e3d0d62f9de7f21c48e634949d218ed6a8278d81367f794ee8b70efa64541d7a4442ccf09e7b4cc3bcbd39398f5c7980ec2
7
- data.tar.gz: ffb85f8c9063aa6e11b8f900e128b4b873e89db117b39e703de6603fc73c014811b63950ff0a0340b0316da11ed2562418f56f1faf42a04491fb0105d913fced
6
+ metadata.gz: 70699c2d05ddb69f64922c6e0e67d46a523b5ed2f70d9278134435a144bc002013fe0657a408a0657ccb9f0fe2d1f9cd7a1521a50e128375b1c1d8ec1e127584
7
+ data.tar.gz: c1125f8dc9af55a1ce1752b94a9bb3273e1153c70e726845867bb91cad9a4b407bcc63537d2bb5859bef20508ce5bfb4391ed56218a690bef2119cf1351597d7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.6.0]
4
+ - Introduce subscriptions CLI. Type `pg-eventstore subscriptions --help` to see available commands. The main purpose of it is to provide the single way to start/stop subscription processes. Check [Subscriptions](docs/subscriptions.md#creating-a-subscription) docs about the new way to start and keep running a subscriptions process.
5
+
3
6
  ## [1.5.0]
4
7
  - Add ability to toggle link events in the admin UI
5
8
  - Mark linked events in the admin UI with "link" icon
data/README.md CHANGED
@@ -49,6 +49,10 @@ Documentation chapters:
49
49
  - [How to make multiple commands atomic](docs/multiple_commands.md)
50
50
  - [Admin UI](docs/admin_ui.md)
51
51
 
52
+ ## CLI
53
+
54
+ The gem is shipped with its own CLI. Use `pg-eventstore --help` to find out its capabilities.
55
+
52
56
  ## Development
53
57
 
54
58
  After checking out the repo, run:
@@ -29,7 +29,7 @@ Now we can use the `#subscribe` method to create the subscription:
29
29
  subscriptions_manager.subscribe('MyAwesomeSubscription', handler: proc { |event| puts event })
30
30
  ```
31
31
 
32
- First argument is the subscription's name. **It must be unique within the subscription set**. Second argument is your subscription's handler where you will be processing your events as they arrive. The example shows the minimum set of arguments required to create the subscription.
32
+ First argument is the subscription's name. **It must be unique within the subscriptions set**. Second argument is your subscription's handler where you will be processing your events as they arrive. The example shows the minimum set of arguments required to create the subscription.
33
33
 
34
34
  In the given state it will be listening to all events from all streams. You can define various filters by providing the `:filter` key of `options` argument:
35
35
 
@@ -50,44 +50,25 @@ subscriptions_manager.start
50
50
  # => PgEventstore::BasicRunner
51
51
  ```
52
52
 
53
- After calling `#start` all subscriptions are locked behind the given subscriptions set and can't be locked by any other subscriptions set. This measure is needed to prevent running the same subscription under the same subscription set using different processes/subscription managers. Such situation will lead to a malformed subscription state and will break its position, meaning the same event will be processed several times. In real world the lock attempt may still happen. This can be common scenario when using kubernetes which rolls out new deployment before shutting down old one. In this case `#start` will return `nil`. You can use this behavior to properly handle such cases. Example:
53
+ After calling `#start` all subscriptions are locked behind the given subscriptions set and can't be locked by any other subscriptions set. This measure is needed to prevent running the same subscription under the same subscription set using different processes/subscription managers. Such situation will lead to a malformed subscription state and will break its position, meaning the same event will be processed several times.
54
54
 
55
- ```ruby
56
- timeout = 20 # 20 seconds
57
- deadline = Time.now + timeout
58
- loop do
59
- break if subscriptions_manager.start
60
- if Time.now > deadline
61
- puts "Failed to acquire subscriptions lock within #{timeout} seconds. Exiting now."
62
- exit
63
- end
64
- sleep 2
65
- end
66
- ```
67
-
68
- To "unlock" the subscription you should gracefully stop the subscription manager:
69
-
70
- ```ruby
71
- subscriptions_manager.stop
72
- ```
73
-
74
- If you shut down the process which runs your subscriptions without calling the `#stop` method, subscriptions will remain locked, and the only way to unlock them will be to call the `#force_lock!` method before calling the `#start` method:
55
+ If, for some reason, you want to lock already locked subscription - you can provide `force_lock: true`:
75
56
 
76
57
  ```ruby
77
- subscriptions_manager.force_lock!
58
+ subscriptions_manager = PgEventstore.subscriptions_manager(subscription_set: 'SubscriptionsOfMyAwesomeMicroservice', force_lock: true)
78
59
  subscriptions_manager.start
79
60
  ```
80
61
 
81
62
  A complete example of the subscription setup process looks like this:
82
63
 
83
64
  ```ruby
84
- require 'pg_eventstore'
85
-
86
65
  PgEventstore.configure do |config|
87
66
  config.pg_uri = ENV.fetch('PG_EVENTSTORE_URI') { 'postgresql://postgres:postgres@localhost:5532/eventstore' }
88
67
  end
89
68
 
90
- subscriptions_manager = PgEventstore.subscriptions_manager(subscription_set: 'MyAwesomeSubscriptions')
69
+ subscriptions_manager = PgEventstore.subscriptions_manager(
70
+ subscription_set: 'MyAwesomeSubscriptions'
71
+ )
91
72
  subscriptions_manager.subscribe(
92
73
  'Foo events Subscription',
93
74
  handler: proc { |event| p "Foo events Subscription: #{event.inspect}" },
@@ -99,40 +80,17 @@ subscriptions_manager.subscribe(
99
80
  options: { filter: { streams: [{ context: 'BarCtx' }] }
100
81
  }
101
82
  )
102
- subscriptions_manager.force_lock! if ENV['FORCE_LOCK'] == 'true'
103
- timeout = 20 # 20 seconds
104
- deadline = Time.now + timeout
105
- loop do
106
- break if subscriptions_manager.start
107
- if Time.now > deadline
108
- puts "Failed to acquire subscriptions lock within #{timeout} seconds. Exiting now."
109
- exit
110
- end
111
- sleep 2
112
- end
83
+ subscriptions_manager.start
84
+ ```
113
85
 
114
- Kernel.trap('TERM') do
115
- puts "Received TERM signal. Stopping Subscriptions Manager and exiting..."
116
- # It is important to wrap subscriptions_manager.stop into another Thread, because it uses Thread::Mutex#synchronize
117
- # internally, but its usage is not allowed inside Kernel.trap block
118
- Thread.new { subscriptions_manager.stop }.join
119
- exit
120
- end
86
+ Persist this script into a file(let's say `subscriptions.rb`). Now it is time to start the process which will be processing those subscriptions. `pg_eventstore` has CLI for that purpose:
121
87
 
122
- loop do
123
- sleep 5
124
- subscriptions_manager.subscriptions.each do |subscription|
125
- puts <<~TEXT
126
- Subscription <<#{subscription.name.inspect}>> is at position #{subscription.current_position}. \
127
- Events processed: #{subscription.total_processed_events}
128
- TEXT
129
- end
130
- puts "Current SubscriptionsSet: #{subscriptions_manager.subscriptions_set}"
131
- puts ""
132
- end
88
+ ```bash
89
+ # -r ./subscriptions.rb will load our subscriptions definitions
90
+ pg-eventstore subscriptions start -r ./subscriptions.rb
133
91
  ```
134
92
 
135
- You can save this script in `subscriptions.rb`, run it with `bundle exec ruby subscriptions.rb`, open another ruby console and test posting different events:
93
+ After running that test subscriptions you can open another ruby console and test posting different events:
136
94
 
137
95
  ```ruby
138
96
  require 'pg_eventstore'
data/exe/pg-eventstore ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "pg_eventstore"
5
+ require "pg_eventstore/cli"
6
+ require "logger"
7
+
8
+ logger = Logger.new(STDOUT)
9
+ logger.level = :info
10
+ logger.progname = "pg_eventstore"
11
+ logger.formatter = proc do |severity, time, progname, msg|
12
+ "\e[36m#{progname} | \e[0m#{time.utc.strftime("%FT%TZ")} #{severity}: #{msg}\n"
13
+ end
14
+
15
+ PgEventstore.logger = logger
16
+ Kernel.exit(PgEventstore::CLI.execute(ARGV))
@@ -95,6 +95,22 @@ module PgEventstore
95
95
  result
96
96
  end
97
97
 
98
+ # @param action [Object]
99
+ # @param filter [Symbol]
100
+ # @param callback [#call]
101
+ # @return [void]
102
+ def remove_callback(action, filter, callback)
103
+ return unless @callbacks.dig(action, filter)
104
+
105
+ @callbacks[action][filter].delete(callback)
106
+ end
107
+
108
+ # Clear all defined callbacks
109
+ # @return [void]
110
+ def clear
111
+ @callbacks.clear
112
+ end
113
+
98
114
  private
99
115
 
100
116
  # @return [void]
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Commands
6
+ class BaseCommand
7
+ module BaseCommandActions
8
+ # @return [Integer] exit code
9
+ def call
10
+ load_external_files
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ # @return [void]
17
+ def load_external_files
18
+ options.requires.each do |file_path|
19
+ require(file_path)
20
+ end
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def inherited(klass)
26
+ super
27
+ klass.prepend BaseCommandActions
28
+ end
29
+ end
30
+
31
+ attr_reader :options
32
+
33
+ # @param options [PgEventstore::CLI::ParserOptions::BaseOptions]
34
+ def initialize(options)
35
+ @options = options
36
+ end
37
+
38
+ # @return [Integer] exit code
39
+ def call
40
+ raise NotImplementedError
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Commands
6
+ module CallbackHandlers
7
+ class StartCmdHandlers
8
+ include Extensions::CallbackHandlersExtension
9
+
10
+ class << self
11
+ # @param subscription_managers [Set<PgEventstore::SubscriptionsManager>]
12
+ # @param manager [PgEventstore::SubscriptionsManager]
13
+ # @return [void]
14
+ def register_managers(subscription_managers, manager)
15
+ subscription_managers.add(manager)
16
+ end
17
+
18
+ # @param action [Proc]
19
+ # @param manager [PgEventstore::SubscriptionsManager]
20
+ # @return [void]
21
+ def handle_start_up(action, manager)
22
+ action.call
23
+ rescue SubscriptionAlreadyLockedError => error
24
+ PgEventstore.logger&.error(
25
+ <<~TEXT
26
+ Subscription #{error.name.inspect} from #{error.set.inspect} set is locked under \
27
+ SubscriptionsSet##{error.lock_id}. Trying to unlock...
28
+ TEXT
29
+ )
30
+ raise unless TryUnlockSubscriptionsSet.try_unlock(manager.config_name, error.lock_id)
31
+ retry
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Commands
6
+ class HelpCommand
7
+ attr_reader :options
8
+
9
+ # @param options [PgEventstore::CLI::ParserOptions::BaseOptions]
10
+ def initialize(options)
11
+ @options = options
12
+ end
13
+
14
+ # @return [Integer] exit code
15
+ def call
16
+ puts options.help
17
+ ExitCodes::SUCCESS
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'callback_handlers/start_cmd_handlers'
4
+
5
+ module PgEventstore
6
+ module CLI
7
+ module Commands
8
+ class StartSubscriptionsCommand < BaseCommand
9
+ # @return [Integer] seconds
10
+ KEEP_ALIVE_INTERVAL = 2
11
+
12
+ def initialize(...)
13
+ super
14
+ @subscription_managers = Set.new
15
+ @running = false
16
+ attach_callbacks
17
+ end
18
+
19
+ # @return [Integer] exit code
20
+ def call
21
+ return ExitCodes::ERROR unless running_subscriptions?
22
+
23
+ @running = true
24
+ setup_killsig
25
+ persist_pid
26
+ keep_process_alive
27
+ ExitCodes::SUCCESS
28
+ rescue SubscriptionAlreadyLockedError => error
29
+ PgEventstore.logger&.error(
30
+ "SubscriptionsSet##{error.lock_id} is still there. Are you stopping it at all?"
31
+ )
32
+ ExitCodes::ERROR
33
+ end
34
+
35
+ private
36
+
37
+ # @return [Boolean]
38
+ def running_subscriptions?
39
+ return true if @subscription_managers.any?(&:running?)
40
+
41
+ PgEventstore.logger&.warn("No subscriptions start ups were detected. Existing...")
42
+ false
43
+ end
44
+
45
+ # @return [void]
46
+ def setup_killsig
47
+ Kernel.trap('TERM') do
48
+ Thread.new do
49
+ PgEventstore.logger&.info("Received TERM signal, stopping subscriptions and exiting...")
50
+ end.join
51
+ # Because the implementation uses Mutex - wrap it into Thread to bypass the limitations of Kernel#trap
52
+ @subscription_managers.map do |manager|
53
+ Thread.new do
54
+ # Initiate graceful shutdown
55
+ manager.stop
56
+ end
57
+ end.each(&:join)
58
+ Utils.remove_file(options.pid_path)
59
+ @running = false
60
+ end
61
+ end
62
+
63
+ # @return [void]
64
+ def persist_pid
65
+ Utils.write_to_file(options.pid_path, Process.pid.to_s)
66
+ end
67
+
68
+ # @return [void]
69
+ def keep_process_alive
70
+ PgEventstore.logger&.info("Startup is successful. Processing subscriptions...")
71
+ loop do
72
+ # SubscriptionsManager#subscriptions_set becomes nil when everything gets stopped.
73
+ if @subscription_managers.all? { |manager| manager.subscriptions_set.nil? }
74
+ PgEventstore.logger&.info("All subscriptions were gracefully shut down. Exiting now...")
75
+ break
76
+ end
77
+ break unless @running
78
+ sleep KEEP_ALIVE_INTERVAL
79
+ end
80
+ end
81
+
82
+ # @return [void]
83
+ def attach_callbacks
84
+ CLI.callbacks.define_callback(
85
+ :start_manager, :before,
86
+ CallbackHandlers::StartCmdHandlers.setup_handler(:register_managers, @subscription_managers)
87
+ )
88
+ CLI.callbacks.define_callback(
89
+ :start_manager, :around,
90
+ CallbackHandlers::StartCmdHandlers.setup_handler(:handle_start_up)
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Commands
6
+ class StopSubscriptionsCommand < BaseCommand
7
+ # @return [Integer] exit code
8
+ def call
9
+ pid = Utils.read_pid(options.pid_path)&.to_i
10
+ if pid && pid > 0
11
+ PgEventstore.logger&.info("Stopping process #{pid}.")
12
+ Process.kill('TERM', pid)
13
+ return ExitCodes::SUCCESS
14
+ end
15
+
16
+ PgEventstore.logger&.error("Pid file #{options.pid_path.inspect} does not exist or empty.")
17
+ ExitCodes::ERROR
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'commands/base_command'
4
+ require_relative 'commands/help_command'
5
+ require_relative 'commands/start_subscriptions_command'
6
+ require_relative 'commands/stop_subscriptions_command'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module ExitCodes
6
+ # @return [Integer]
7
+ SUCCESS = 0
8
+ # @return [Integer]
9
+ ERROR = 1
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module ParserOptions
6
+ class BaseOptions
7
+ include Extensions::OptionsExtension
8
+
9
+ option(:help, metadata: Metadata.new(short: '-h', long: '--help', description: 'Prints this help'))
10
+ option(
11
+ :requires,
12
+ metadata: Metadata.new(
13
+ short: '-rFILE_PATH',
14
+ long: '--require=FILE_PATH',
15
+ description: 'Ruby files to load. You can provide this option multiple times to load more files.'
16
+ )
17
+ ) do
18
+ []
19
+ end
20
+
21
+ # @param parser [OptionParser]
22
+ # @return [void]
23
+ def attach_parser_handlers(parser)
24
+ parser.on(*to_parser_opts(:help)) do
25
+ self.help = parser.to_s
26
+ end
27
+ parser.on(*to_parser_opts(:requires)) do |path|
28
+ requires.push(path)
29
+ end
30
+ end
31
+
32
+ # @param option [Symbol]
33
+ # @return [Array<String>]
34
+ def to_parser_opts(option)
35
+ option(option).metadata.to_parser_opts
36
+ end
37
+
38
+ # @param option [Symbol]
39
+ # @return [PgEventstore::Extensions::OptionsExtension::Option]
40
+ def option(option)
41
+ self.class.options[option]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module ParserOptions
6
+ class DefaultOptions < BaseOptions
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module ParserOptions
6
+ class Metadata
7
+ include Extensions::OptionsExtension
8
+
9
+ option(:short)
10
+ option(:long)
11
+ option(:description)
12
+
13
+ # @return [Array<String>]
14
+ def to_parser_opts
15
+ [short, long, description]
16
+ end
17
+
18
+ # @return [Integer]
19
+ def hash
20
+ to_parser_opts.hash
21
+ end
22
+
23
+ # @param another [Object]
24
+ # @return [Boolean]
25
+ def ==(another)
26
+ return false unless another.is_a?(Metadata)
27
+
28
+ to_parser_opts == another.to_parser_opts
29
+ end
30
+ alias eql? ==
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module ParserOptions
6
+ class SubscriptionOptions < BaseOptions
7
+ option(
8
+ :pid_path,
9
+ metadata: Metadata.new(
10
+ short: '-pFILE_PATH',
11
+ long: '--pid=FILE_PATH',
12
+ description: 'Defines pid file path. Defaults to /tmp/pg-es_subscriptions.pid'
13
+ )
14
+ ) do
15
+ '/tmp/pg-es_subscriptions.pid'
16
+ end
17
+
18
+ # @param parser [OptionParser]
19
+ # @return [void]
20
+ def attach_parser_handlers(parser)
21
+ super
22
+ %i[pid_path].each do |option|
23
+ parser.on(*to_parser_opts(option)) do |value|
24
+ public_send("#{option}=", value)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser_options/metadata'
4
+ require_relative 'parser_options/base_options'
5
+ require_relative 'parser_options/default_options'
6
+ require_relative 'parser_options/subscription_options'
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Parsers
6
+ class BaseParser
7
+ class << self
8
+ # @return [String]
9
+ def banner
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+
14
+ attr_reader :args, :options
15
+
16
+ # @param args [Array<String>]
17
+ # @param options [PgEventstore::CLI::ParserOptions::BaseOptions]
18
+ def initialize(args, options)
19
+ @args = args
20
+ @options = options
21
+ @parser = ::OptionParser.new(self.class.banner)
22
+ end
23
+
24
+ # @return [Array<Array<String>, PgEventstore::CLI::ParserOptions::BaseOptions>] list of commands and parsed
25
+ # options
26
+ def parse
27
+ @options.attach_parser_handlers(@parser)
28
+ [@parser.parse(args), @options]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Parsers
6
+ class DefaultParser < BaseParser
7
+ class << self
8
+ # @return [String]
9
+ def banner
10
+ <<~TEXT
11
+ Usage: pg-eventstore [options]
12
+ pg-eventstore [command]
13
+
14
+ Commands:
15
+ subscriptions Start, stop subscriptions
16
+
17
+ Options:
18
+ TEXT
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ module Parsers
6
+ class SubscriptionParser < BaseParser
7
+ class << self
8
+ # @return [String]
9
+ def banner
10
+ <<~TEXT
11
+ Usage: pg-eventstore subscriptions [command] [options]
12
+
13
+ Commands:
14
+ start Start subscriptions. Example: pg-eventstore subscriptions start -r lib/my_subscriptions.rb
15
+ stop Stop subscriptions. Example: pg-eventstore subscriptions stop
16
+
17
+ Options:
18
+ TEXT
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parsers/base_parser'
4
+ require_relative 'parsers/default_parser'
5
+ require_relative 'parsers/subscription_parser'