potluck 0.0.6 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6881b14c35b5ae6be793e8390d8ab87d1b8220ea78a2803dac55f5ea8c2bc4cf
4
- data.tar.gz: e0c7b0e3a472f03dded30e35e38fbad4f8f484a1d3cb44cd4ced99248cdd298c
3
+ metadata.gz: 9827c82165e843be1296376b9fc09df5802b8a5935094dfe9a0ee25543ed8780
4
+ data.tar.gz: fc963b6df8213041917fb4f8f44bd257180883d058ac3e365adb4d5a8357c201
5
5
  SHA512:
6
- metadata.gz: 0ce2edbef44e0f8e27ffb0bb63e717d5f9781f29a27661b72de6bd77b0421f73757a230f5d3193f8e04bbaba8abd9e282d8021af9c4aded66e5e10efd78bb2c7
7
- data.tar.gz: 5246bb757a883318ed514c8a5a5a94d5631732170c9622d1b20f92541a419fb36b9b559a3a6967ca700d2ba765ec58a323fed3b39fb464f222a9ca9dfc84e77b
6
+ metadata.gz: 4dabce647594c290c42a1db1b5c0100ae618ee1e91998272978bae09bd8f89ee6d9838dab506f5dd0dc83532f00ec2c045086f06b02197da2fda4152ca9d3536
7
+ data.tar.gz: efd26d28db394a6186cd719fa5aca62137eb742b328d50fb9a0cd636f265438a4b047141330657453ebe9d0aa074ed7297541d7dae80eca76aeeb53a63a53c9b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.8
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Potluck
4
+ # Public: Configuration settings for Potluck services.
5
+ class Config
6
+ DEFAULT_DIR = File.expand_path(File.join(Dir.home, '.potluck')).freeze
7
+ OLD_HOMEBREW_PREFIX = '/usr/local'
8
+
9
+ attr_writer(:dir, :homebrew_prefix)
10
+
11
+ # Public: Create a new instance.
12
+ #
13
+ # Yields the instance being created.
14
+ #
15
+ # Examples
16
+ #
17
+ # Config.new do |config|
18
+ # config.dir = '/etc/potluck'
19
+ # config.homebrew_prefix = '/custom/brew'
20
+ # end
21
+ def initialize
22
+ yield(self) if block_given?
23
+ end
24
+
25
+ # Public: Get the directory path setting.
26
+ #
27
+ # Returns the String value.
28
+ def dir
29
+ @dir || DEFAULT_DIR
30
+ end
31
+
32
+ # Public: Get the Homebrew prefix path setting.
33
+ #
34
+ # Returns the String value.
35
+ def homebrew_prefix
36
+ @homebrew_prefix || ENV['HOMEBREW_PREFIX'] || OLD_HOMEBREW_PREFIX
37
+ end
38
+ end
39
+ end
@@ -1,32 +1,108 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require('English')
3
4
  require('fileutils')
4
5
 
5
6
  module Potluck
6
- ##
7
- # General error class used for errors encountered with a service.
8
- #
7
+ # Public: General error class used for errors encountered with a service.
9
8
  class ServiceError < StandardError; end
10
9
 
11
- ##
12
- # A Ruby interface for configuring, controlling, and interacting with external processes. Serves as a
13
- # parent class for service-specific child classes.
14
- #
10
+ # Public: A Ruby interface for configuring, controlling, and interacting with external processes. Serves
11
+ # as a parent class for service-specific child classes.
15
12
  class Service
16
13
  SERVICE_PREFIX = 'potluck.npickens.'
17
- LAUNCHCTL_ERROR_REGEX = /^-|\t[^0]\t/.freeze
14
+ LAUNCHCTL_ERROR_REGEX = /^-|\t[^0]\t/
18
15
 
19
- ##
20
- # Creates a new instance.
16
+ # Public: Get the human-friendly name of the service.
21
17
  #
22
- # * +logger+ - +Logger+ instance to use for outputting info and error messages (optional). Output will
23
- # be sent to stdout and stderr if none is supplied.
24
- # * +manage+ - True if the service runs locally and should be managed by this process (default: true if
25
- # launchctl is available and false otherwise).
18
+ # Returns the String name.
19
+ def self.pretty_name
20
+ @pretty_name ||= to_s.split('::').last
21
+ end
22
+
23
+ # Public: Get the computer-friendly name of the service.
24
+ #
25
+ # Returns the String name.
26
+ def self.service_name
27
+ @service_name ||= pretty_name.downcase
28
+ end
29
+
30
+ # Public: Get the name for the launchctl service.
31
+ #
32
+ # Returns the String name.
33
+ def self.launchctl_name
34
+ "#{SERVICE_PREFIX}#{service_name}"
35
+ end
36
+
37
+ # Public: Get the path to the launchctl plist file of the service.
38
+ #
39
+ # Returns the String path.
40
+ def self.plist_path
41
+ File.join(Potluck.config.dir, "#{launchctl_name}.plist")
42
+ end
43
+
44
+ # Public: Get the content of the launchctl plist file.
45
+ #
46
+ # Returns the String content.
47
+ def self.plist(content = '')
48
+ <<~XML
49
+ <?xml version="1.0" encoding="UTF-8"?>
50
+ #{'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1' \
51
+ '.0.dtd">'}
52
+ <plist version="1.0">
53
+ <dict>
54
+ <key>Label</key>
55
+ <string>#{launchctl_name}</string>
56
+ <key>RunAtLoad</key>
57
+ <true/>
58
+ <key>KeepAlive</key>
59
+ <false/>
60
+ #{content.gsub(/^/, ' ').strip}
61
+ </dict>
62
+ </plist>
63
+ XML
64
+ end
65
+
66
+ # Public: Write the service's launchctl plist file to disk.
67
+ #
68
+ # Returns nothing.
69
+ def self.write_plist
70
+ FileUtils.mkdir_p(File.dirname(plist_path))
71
+ File.write(plist_path, plist)
72
+ end
73
+
74
+ # Public: Check if launchctl is available.
26
75
  #
76
+ # Returns the boolean result.
77
+ def self.launchctl?
78
+ return @@launchctl if defined?(@@launchctl)
79
+
80
+ `which launchctl 2>&1`
81
+
82
+ @@launchctl = $CHILD_STATUS.success?
83
+ end
84
+
85
+ # Public: Raise an error if launchctl is not available.
86
+ #
87
+ # Returns true if launchctl is available.
88
+ # Raises ServiceError if launchctl is not available.
89
+ def self.ensure_launchctl!
90
+ launchctl? || raise(ServiceError, "Cannot manage #{pretty_name}: launchctl not found")
91
+ end
92
+
93
+ # Public: Create a new instance.
94
+ #
95
+ # logger: - Logger instance to use in place of sending output to stdin and stderr.
96
+ # manage: - Boolean specifying if the service runs locally and should be managed by this process
97
+ # (defaults to whether or not launchctl is available); or a configuration Hash:
98
+ #
99
+ # status: - String command for fetching the status of the service.
100
+ # status_error_regex: - Regexp that determines if the service is in an error state.
101
+ # start: - String command for starting the service.
102
+ # stop: - String command for stopping the service.
27
103
  def initialize(logger: nil, manage: self.class.launchctl?)
28
104
  @logger = logger
29
- @manage = !!manage
105
+ @manage = manage
30
106
  @manage_with_launchctl = false
31
107
 
32
108
  if manage.kind_of?(Hash)
@@ -40,33 +116,30 @@ module Potluck
40
116
  end
41
117
  end
42
118
 
43
- ##
44
- # Returns true if the service is managed.
119
+ # Public: Check if the service is managed by this process.
45
120
  #
121
+ # Returns the boolean result.
46
122
  def manage?
47
123
  @manage
48
124
  end
49
125
 
50
- ##
51
- # Returns true if the service is managed via launchctl.
126
+ # Public: Check if the service is managed by launchctl.
52
127
  #
128
+ # Returns the boolean result.
53
129
  def manage_with_launchctl?
54
130
  @manage_with_launchctl
55
131
  end
56
132
 
57
- ##
58
- # Returns the status of the service:
59
- #
60
- # * +:active+ if the service is managed and running.
61
- # * +:inactive+ if the service is not managed or is not running.
62
- # * +:error+ if the service is managed and is in an error state.
133
+ # Public: Get the status of the service.
63
134
  #
135
+ # Returns :active if the service is managed and running, :inactive if the service is not managed or is
136
+ # not running, or :error if the service is managed and is in an error state.
64
137
  def status
65
138
  return :inactive unless manage?
66
139
 
67
140
  output = `#{status_command}`
68
141
 
69
- if $? != 0
142
+ if !$CHILD_STATUS.success?
70
143
  :inactive
71
144
  elsif status_error_regex && output[status_error_regex]
72
145
  :error
@@ -75,9 +148,9 @@ module Potluck
75
148
  end
76
149
  end
77
150
 
78
- ##
79
- # Starts the service if it's managed and is not active.
151
+ # Public: Start the service if it's managed and is not active.
80
152
  #
153
+ # Returns nothing.
81
154
  def start
82
155
  return unless manage?
83
156
 
@@ -95,9 +168,10 @@ module Potluck
95
168
  log("#{self.class.pretty_name} started")
96
169
  end
97
170
 
98
- ##
99
- # Stops the service if it's managed and is active or in an error state.
171
+ # Public: Stop the service if it's managed and is active or in an error state.
100
172
  #
173
+ # Returns nothing.
174
+ # Raises ServiceError if the service could not be stopped.
101
175
  def stop
102
176
  return unless manage? && status != :inactive
103
177
 
@@ -110,9 +184,9 @@ module Potluck
110
184
  log("#{self.class.pretty_name} stopped")
111
185
  end
112
186
 
113
- ##
114
- # Restarts the service if it's managed by calling stop and then start.
187
+ # Public: Restart the service if it's managed.
115
188
  #
189
+ # Returns nothing.
116
190
  def restart
117
191
  return unless manage?
118
192
 
@@ -120,31 +194,32 @@ module Potluck
120
194
  start
121
195
  end
122
196
 
123
- ##
124
- # Runs a command with the default shell. Raises an error if the command exits with a non-zero status.
197
+ # Public: Run a command with the default shell.
125
198
  #
126
- # * +command+ - Command to run.
127
- # * +capture_stderr+ - True if stderr should be redirected to stdout; otherwise stderr output will not
128
- # be logged (default: true).
199
+ # command - String command to run.
200
+ # capture_stderr: - Boolean specifying if stderr should be redirected to stdout (if false, stderr output
201
+ # will not be logged).
129
202
  #
203
+ # Returns the String output of the command.
204
+ # Raises ServiceError if the command exited with a non-zero status.
130
205
  def run(command, capture_stderr: true)
131
206
  output = `#{command}#{' 2>&1' if capture_stderr}`
132
- status = $?
207
+ status = $CHILD_STATUS
133
208
 
134
- if status != 0
209
+ if status.success?
210
+ output
211
+ else
135
212
  output.split("\n").each { |line| log(line, :error) }
136
213
  raise(ServiceError, "Command exited with status #{status.exitstatus}: #{command}")
137
- else
138
- output
139
214
  end
140
215
  end
141
216
 
142
- ##
143
- # Logs a message using the logger or stdout/stderr if no logger is configured.
217
+ # Public: Log a message using the logger or stdout/stderr if no logger is configured.
144
218
  #
145
- # * +message+ - Message to log.
146
- # * +error+ - True if the message is an error (default: false).
219
+ # message - String message to log.
220
+ # error - Boolean specifying if the message is an error.
147
221
  #
222
+ # Returns nothing.
148
223
  def log(message, error = false)
149
224
  if @logger
150
225
  error ? @logger.error(message) : @logger.info(message)
@@ -153,119 +228,47 @@ module Potluck
153
228
  end
154
229
  end
155
230
 
156
- ##
157
- # Human-friendly name of the service.
158
- #
159
- def self.pretty_name
160
- @pretty_name ||= self.to_s.split('::').last
161
- end
162
-
163
- ##
164
- # Computer-friendly name of the service.
165
- #
166
- def self.service_name
167
- @service_name ||= pretty_name.downcase
168
- end
169
-
170
- ##
171
- # Name for the launchctl service.
172
- #
173
- def self.launchctl_name
174
- "#{SERVICE_PREFIX}#{service_name}"
175
- end
176
-
177
- ##
178
- # Path to the launchctl plist file of the service.
179
- #
180
- def self.plist_path
181
- File.join(DIR, "#{launchctl_name}.plist")
182
- end
183
-
184
- ##
185
- # Content of the launchctl plist file.
186
- #
187
- def self.plist(content = '')
188
- <<~EOS
189
- <?xml version="1.0" encoding="UTF-8"?>
190
- #{'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.'\
191
- '0.dtd">'}
192
- <plist version="1.0">
193
- <dict>
194
- <key>Label</key>
195
- <string>#{launchctl_name}</string>
196
- <key>RunAtLoad</key>
197
- <true/>
198
- <key>KeepAlive</key>
199
- <false/>
200
- #{content.gsub(/^/, ' ').strip}
201
- </dict>
202
- </plist>
203
- EOS
204
- end
205
-
206
- ##
207
- # Writes the service's launchctl plist file to disk.
208
- #
209
- def self.write_plist
210
- FileUtils.mkdir_p(File.dirname(plist_path))
211
- File.write(plist_path, plist)
212
- end
213
-
214
- ##
215
- # Returns true if launchctl is available.
216
- #
217
- def self.launchctl?
218
- defined?(@@launchctl) ? @@launchctl : (@@launchctl = `which launchctl 2>&1` && $? == 0)
219
- end
220
-
221
- ##
222
- # Checks if launchctl is available and raises an error if not.
223
- #
224
- def self.ensure_launchctl!
225
- launchctl? || raise(ServiceError, "Cannot manage #{pretty_name}: launchctl not found")
226
- end
227
-
228
231
  private
229
232
 
230
- ##
231
- # Command to get the status of the service.
233
+ # Internal: Get the command for fetching the status of the service.
232
234
  #
235
+ # Returns the String command.
233
236
  def status_command
234
237
  @status_command || "launchctl list 2>&1 | grep #{SERVICE_PREFIX}#{self.class.service_name}"
235
238
  end
236
239
 
237
- ##
238
- # Regular expression to check the output of +#status_command+ against to determine if the service is in
239
- # an error state.
240
+ # Internal: Get the regex used to determine if the service is in an error state (by matching the output
241
+ # of the status command against it).
240
242
  #
243
+ # Returns the Regexp.
241
244
  def status_error_regex
242
245
  @status_error_regex || LAUNCHCTL_ERROR_REGEX
243
246
  end
244
247
 
245
- ##
246
- # Command to start the service.
248
+ # Internal: Get the command for starting the service.
247
249
  #
250
+ # Returns the String command.
248
251
  def start_command
249
252
  @start_command || "launchctl bootstrap gui/#{Process.uid} #{self.class.plist_path}"
250
253
  end
251
254
 
252
- ##
253
- # Command to stop the service.
255
+ # Internal: Get the command for stopping the service.
254
256
  #
257
+ # Returns the String command.
255
258
  def stop_command
256
259
  @stop_command || "launchctl bootout gui/#{Process.uid}/#{self.class.launchctl_name}"
257
260
  end
258
261
 
259
- ##
260
- # Calls the supplied block repeatedly until it returns false. Checks frequently at first and gradually
261
- # reduces down to one-second intervals.
262
+ # Internal: Call the supplied block repeatedly until it returns false. Checks frequently at first and
263
+ # gradually reduces down to one-second intervals.
262
264
  #
263
- # * +timeout+ - Maximum number of seconds to wait before timing out (default: 30).
264
- # * +block+ - Block to call until it returns false.
265
+ # timeout - Integer maximum number of seconds to wait before timing out.
266
+ # block - Proc to call until it returns false.
265
267
  #
268
+ # Returns nothing.
266
269
  def wait(timeout = 30, &block)
267
270
  while block.call && timeout > 0
268
- reduce = [[(30 - timeout.to_i) / 5.0, 0.1].max, 1].min
271
+ reduce = ((30 - timeout.to_i) / 5.0).clamp(0.1, 1)
269
272
  timeout -= reduce
270
273
 
271
274
  sleep(reduce)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Potluck
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.8'
5
5
  end
data/lib/potluck.rb CHANGED
@@ -1,8 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative('potluck/config')
3
4
  require_relative('potluck/service')
5
+ require_relative('potluck/version')
4
6
 
7
+ # Public: Main module providing an extensible Ruby framework for managing external processes.
5
8
  module Potluck
6
- DIR = File.expand_path(File.join(ENV['HOME'], '.potluck')).freeze
7
- IS_MACOS = !!RUBY_PLATFORM[/darwin/]
9
+ @config = Config.new
10
+
11
+ class << self
12
+ attr_accessor(:config)
13
+ end
14
+
15
+ # Public: Change settings.
16
+ #
17
+ # Yields the Config instance used by Potluck services.
18
+ #
19
+ # Examples
20
+ #
21
+ # Potluck.configure do |config|
22
+ # config.dir = '/etc/potluck'
23
+ # config.homebrew_prefix = '/custom/brew'
24
+ # end
25
+ #
26
+ # Returns nothing.
27
+ def self.configure
28
+ yield(config)
29
+ end
8
30
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: potluck
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Pickens
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-01-21 00:00:00.000000000 Z
10
+ date: 2025-03-19 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -25,30 +24,51 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '2.0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: minitest
27
+ name: logger
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - ">="
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.6.6
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: 5.11.2
34
- - - "<"
39
+ version: 1.6.6
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
35
45
  - !ruby/object:Gem::Version
36
- version: 6.0.0
46
+ version: '5.24'
37
47
  type: :development
38
48
  prerelease: false
39
49
  version_requirements: !ruby/object:Gem::Requirement
40
50
  requirements:
41
- - - ">="
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.24'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest-reporters
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
42
59
  - !ruby/object:Gem::Version
43
- version: 5.11.2
44
- - - "<"
60
+ version: '1.7'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
45
66
  - !ruby/object:Gem::Version
46
- version: 6.0.0
67
+ version: '1.7'
47
68
  description: Potluck provides a simple interface for managing external processes in
48
69
  a way that plays nice with others as well as smoothly handling both development
49
70
  and production environments. Current official gem extensions provide Nginx and Postgres
50
71
  management.
51
- email:
52
72
  executables: []
53
73
  extensions: []
54
74
  extra_rdoc_files: []
@@ -57,16 +77,16 @@ files:
57
77
  - README.md
58
78
  - VERSION
59
79
  - lib/potluck.rb
80
+ - lib/potluck/config.rb
60
81
  - lib/potluck/service.rb
61
82
  - lib/potluck/version.rb
62
83
  homepage: https://github.com/npickens/potluck
63
84
  licenses:
64
85
  - MIT
65
86
  metadata:
66
- allowed_push_host: https://rubygems.org
67
- homepage_uri: https://github.com/npickens/potluck
68
- source_code_uri: https://github.com/npickens/potluck
69
- post_install_message:
87
+ bug_tracker_uri: https://github.com/npickens/potluck/issues
88
+ documentation_uri: https://github.com/npickens/potluck/blob/0.0.8/README.md
89
+ source_code_uri: https://github.com/npickens/potluck/tree/0.0.8
70
90
  rdoc_options: []
71
91
  require_paths:
72
92
  - lib
@@ -74,15 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
94
  requirements:
75
95
  - - ">="
76
96
  - !ruby/object:Gem::Version
77
- version: 2.5.8
97
+ version: 3.0.0
78
98
  required_rubygems_version: !ruby/object:Gem::Requirement
79
99
  requirements:
80
100
  - - ">="
81
101
  - !ruby/object:Gem::Version
82
- version: '0'
102
+ version: 2.0.0
83
103
  requirements: []
84
- rubygems_version: 3.2.32
85
- signing_key:
104
+ rubygems_version: 3.6.6
86
105
  specification_version: 4
87
106
  summary: An extensible Ruby framework for managing external processes.
88
107
  test_files: []