cobra_commander 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobraCommander
4
+ module Executor
5
+ # @private
6
+ #
7
+ # This WorkerPooll will queue up jobs, and execute them using
8
+ # Worker's, each with a thread running our work loop using the
9
+ # given runner.
10
+ #
11
+ # - A *job* is defined by a group of arguments to be passed to the runner.
12
+ # - A *runner* is an object that respond to #call(tty, *args), where TTY is
13
+ # an instance of TTY::Command, and *args are the arguments queued in the
14
+ # worker pool.
15
+ # - The *runner* call method must return an array of [status, output]
16
+ # - A worker manages a thread running the job wirh runner.call and updates
17
+ # the job result and output.
18
+ #
19
+ class WorkerPool
20
+ Job = Struct.new(:name, :args) do
21
+ attr_reader :output, :status
22
+
23
+ def resolve!(status, output)
24
+ @status = status
25
+ @output = output
26
+ end
27
+
28
+ def resolved?
29
+ !@status.nil?
30
+ end
31
+ end
32
+
33
+ class Worker
34
+ attr_reader :id
35
+
36
+ def initialize(id, pool)
37
+ @id = id
38
+ @pool = pool
39
+ end
40
+
41
+ def kill
42
+ @thread&.kill
43
+ end
44
+
45
+ def spawn
46
+ @thread = Thread.new do
47
+ loop { break if @pool.run_next == :exit }
48
+ end
49
+ end
50
+
51
+ def wait
52
+ @thread.join
53
+ end
54
+ end
55
+
56
+ attr_reader :jobs
57
+
58
+ def initialize(runner:, workers:, printer:, jobs:, &name_f)
59
+ @tty = ::CobraCommander::Executor::IsolatedPTY.new(printer: printer)
60
+ @runner = runner
61
+ @workers = Array.new(workers) { |id| Worker.new(id, self) }
62
+ @jobs = []
63
+ @queue = Queue.new
64
+
65
+ push_all(jobs, &(name_f || :describe))
66
+ end
67
+
68
+ def error?
69
+ jobs.map(&:status).any?(:error)
70
+ end
71
+
72
+ def push_all(jobs, &name_f)
73
+ jobs.each { push(name_f&.(_1), *Array(_1)) }
74
+ end
75
+
76
+ def push(name, *args)
77
+ Job.new(name, args).tap do |job|
78
+ @jobs.push(job)
79
+ @queue.push(job)
80
+ end
81
+ end
82
+
83
+ def start
84
+ @stop = false
85
+ @workers.each(&:spawn)
86
+ @queue.close
87
+ begin
88
+ @workers.each(&:wait)
89
+ rescue Interrupt
90
+ @workers.each(&:kill) if @stop
91
+ @stop = true
92
+ retry
93
+ end
94
+ end
95
+
96
+ def run_next
97
+ return :exit if @stop
98
+ return :exit unless (job = @queue.pop)
99
+
100
+ job.resolve!(*@runner.call(@tty, *job.args))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,45 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "executor/execution"
4
- require_relative "executor/job"
5
- require_relative "executor/script"
6
- require_relative "executor/command"
7
- require_relative "executor/spinners"
8
- require_relative "executor/interactive_printer"
9
- require_relative "executor/markdown_printer"
3
+ require "pastel"
4
+ require "tty-command"
5
+ require "tty-prompt"
10
6
 
11
7
  module CobraCommander
12
8
  # Execute a command on all given packages
13
9
  module Executor
10
+ autoload :BufferedPrinter, "cobra_commander/executor/buffered_printer"
11
+ autoload :Command, "cobra_commander/executor/command"
12
+ autoload :OutputPrompt, "cobra_commander/executor/output_prompt"
13
+ autoload :IsolatedPTY, "cobra_commander/executor/isolated_pty"
14
+ autoload :PackageCriteria, "cobra_commander/executor/package_criteria"
15
+ autoload :Printers, "cobra_commander/executor/printers"
16
+ autoload :RunScript, "cobra_commander/executor/run_script"
17
+ autoload :Script, "cobra_commander/executor/script"
18
+ autoload :WorkerPool, "cobra_commander/executor/worker_pool"
19
+
14
20
  module_function
15
21
 
16
- # Executes the given jobs in an CobraCommander::Executor::Execution.
17
- #
18
- # This facade also allows to execute the jobs with a spinner (@see CobraCommander::Executor::Spinners) to display
19
- # the execution status of each job.
22
+ # Executes the given jobs in an CobraCommander::Executor::WorkerPool.
20
23
  #
21
- # You can also determine how to display the execution once it's completed, by setting `output_mode` to either
22
- # :interactive or :markdown. When using :interactive, a menu with each job will be displayed allowing the user
23
- # to select a job and see its output. When using :markdown, a markdown will be printed to `output` with the
24
- # output of each job.
24
+ # When only one job is queued, it choses the :quiet printer, to print the
25
+ # output as it happens.
26
+ # When more than one job is queued in interactive mode, it uses the :progress
27
+ # printer, which will display a green dot or a red F depending on the result
28
+ # of each script execution.
29
+ # If not in interactive mode, it will print the output in a buffered way to
30
+ # make it easier to read each output.
25
31
  #
26
32
  # @param jobs [Enumerable<CobraCommander::Executor::Job>] the jobs to run
27
- # @param status_output [IO,nil] if not nil, will print the spinners for each job in this output
28
- # @param workers [Integer] number of workers processing the jobs queue (see CobraCommander::Executor::Execution)
29
- # @param output_mode [:interactive,:markdown,nil] how the output will be printed after execution
30
- # @param workers [Integer] number of workers processing the jobs queue (see CobraCommander::Executor::Execution)
31
- # @return [CobraCommander::Executor::Execution]
32
- # @see CobraCommander::Executor::Execution
33
- # @see CobraCommander::Executor::Spinners
34
- # @see CobraCommander::Executor::InterativePrinter
35
- # @see CobraCommander::Executor::MarkdownPrinter
36
- def execute(jobs:, status_output: nil, output_mode: nil, output: nil, **kwargs)
37
- Execution.new(jobs, **kwargs).tap do |execution|
38
- Spinners.start(execution, output: status_output) if status_output
39
- execution.wait
40
- InteractivePrinter.run(execution, output) if output_mode == :interactive
41
- MarkdownPrinter.run(execution, output) if output_mode == :markdown
42
- end
33
+ # @param interactive [Boolean] prefer interactive output
34
+ # @see CobraCommander::Executor::WorkerPool for more options
35
+ def execute_and_handle_exit(jobs:, interactive: false, **kwargs, &name_f)
36
+ printer = if jobs.size == 1 then :quiet
37
+ elsif interactive then :progress
38
+ else
39
+ ::CobraCommander::Executor::BufferedPrinter
40
+ end
41
+ pool = WorkerPool.new(jobs: jobs, printer: printer, **kwargs, &name_f).tap(&:start)
42
+ return CobraCommander::Executor::OutputPrompt.run(pool) if interactive && jobs.size > 1
43
+
44
+ exit(1) if pool.error?
43
45
  end
44
46
  end
45
47
  end
@@ -17,5 +17,9 @@ module CobraCommander
17
17
  @name = name
18
18
  @dependencies = dependencies
19
19
  end
20
+
21
+ def describe
22
+ "#{name} (#{key})"
23
+ end
20
24
  end
21
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CobraCommander
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cobra_commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Langfeld
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-01-05 00:00:00.000000000 Z
13
+ date: 2023-03-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -26,20 +26,6 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '0'
29
- - !ruby/object:Gem::Dependency
30
- name: concurrent-ruby
31
- requirement: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - "~>"
34
- - !ruby/object:Gem::Version
35
- version: '1.1'
36
- type: :runtime
37
- prerelease: false
38
- version_requirements: !ruby/object:Gem::Requirement
39
- requirements:
40
- - - "~>"
41
- - !ruby/object:Gem::Version
42
- version: '1.1'
43
29
  - !ruby/object:Gem::Dependency
44
30
  name: thor
45
31
  requirement: !ruby/object:Gem::Requirement
@@ -88,20 +74,6 @@ dependencies:
88
74
  - - "~>"
89
75
  - !ruby/object:Gem::Version
90
76
  version: 0.23.1
91
- - !ruby/object:Gem::Dependency
92
- name: tty-spinner
93
- requirement: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - "~>"
96
- - !ruby/object:Gem::Version
97
- version: 0.9.3
98
- type: :runtime
99
- prerelease: false
100
- version_requirements: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - "~>"
103
- - !ruby/object:Gem::Version
104
- version: 0.9.3
105
77
  - !ruby/object:Gem::Dependency
106
78
  name: aruba
107
79
  requirement: !ruby/object:Gem::Requirement
@@ -241,16 +213,7 @@ executables:
241
213
  extensions: []
242
214
  extra_rdoc_files: []
243
215
  files:
244
- - ".gitignore"
245
- - ".rspec"
246
- - ".rubocop.yml"
247
- - Gemfile
248
- - Guardfile
249
- - Rakefile
250
- - bin/console
251
- - bin/setup
252
216
  - cobra_commander.gemspec
253
- - doc/dependency_decisions.yml
254
217
  - docs/CHANGELOG.md
255
218
  - docs/README.md
256
219
  - exe/cobra
@@ -263,21 +226,20 @@ files:
263
226
  - lib/cobra_commander/cli/output/dot_graph.rb
264
227
  - lib/cobra_commander/component.rb
265
228
  - lib/cobra_commander/executor.rb
229
+ - lib/cobra_commander/executor/buffered_printer.rb
266
230
  - lib/cobra_commander/executor/command.rb
267
- - lib/cobra_commander/executor/execution.rb
268
- - lib/cobra_commander/executor/interactive_printer.rb
269
- - lib/cobra_commander/executor/job.rb
270
- - lib/cobra_commander/executor/markdown_printer.rb
231
+ - lib/cobra_commander/executor/isolated_pty.rb
232
+ - lib/cobra_commander/executor/output_prompt.rb
271
233
  - lib/cobra_commander/executor/package_criteria.rb
234
+ - lib/cobra_commander/executor/run_script.rb
272
235
  - lib/cobra_commander/executor/script.rb
273
- - lib/cobra_commander/executor/spinners.rb
236
+ - lib/cobra_commander/executor/worker_pool.rb
274
237
  - lib/cobra_commander/git_changed.rb
275
238
  - lib/cobra_commander/package.rb
276
239
  - lib/cobra_commander/registry.rb
277
240
  - lib/cobra_commander/source.rb
278
241
  - lib/cobra_commander/umbrella.rb
279
242
  - lib/cobra_commander/version.rb
280
- - mkdocs.yml
281
243
  homepage: http://tech.powerhrg.com/cobra_commander/
282
244
  licenses:
283
245
  - MIT
@@ -301,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
301
263
  - !ruby/object:Gem::Version
302
264
  version: '0'
303
265
  requirements: []
304
- rubygems_version: 3.4.1
266
+ rubygems_version: 3.4.6
305
267
  signing_key:
306
268
  specification_version: 4
307
269
  summary: Tools for working with Component Based Rails Apps
data/.gitignore DELETED
@@ -1,16 +0,0 @@
1
- /.bundle/
2
- /.DS_store
3
- /.yardoc
4
- /Gemfile.lock
5
- /_yardoc/
6
- /coverage/
7
- /graph.*
8
- /pkg/
9
- /spec/examples.txt
10
- /spec/reports/
11
- /spec/fixtures/app/node_modules/
12
- /spec/fixtures/app/node_manifest/node_modules/
13
- /spec/fixtures/app/components/*/node_modules/
14
- /tmp/
15
-
16
- .bundle
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,8 +0,0 @@
1
- require:
2
- - rubocop-powerhome
3
-
4
- Rails:
5
- Enabled: false
6
-
7
- Style/ClassAndModuleChildren:
8
- Enabled: false
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- bundler_version = ENV.fetch("BUNDLER_VERSION", "2")
8
- gem "bundler", "~> #{bundler_version}"
9
-
10
- group :cobra do
11
- gem "cobra_commander-stub", path: "./spec/support/", require: "stub_source"
12
- end
data/Guardfile DELETED
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- guard :rspec, cmd: "bundle exec rspec" do
4
- require "guard/rspec/dsl"
5
- dsl = Guard::RSpec::Dsl.new(self)
6
-
7
- rspec = dsl.rspec
8
- watch(rspec.spec_helper) { rspec.spec_dir }
9
- watch(rspec.spec_support) { rspec.spec_dir }
10
- watch(rspec.spec_files)
11
-
12
- ruby = dsl.ruby
13
- dsl.watch_spec_files_for(ruby.lib_files)
14
- end
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rubocop/rake_task"
5
- require "rspec/core/rake_task"
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
- RuboCop::RakeTask.new(:rubocop)
9
-
10
- task default: %i[spec rubocop]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "cobra_commander"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,9 +0,0 @@
1
- ---
2
- - - :inherit_from
3
- - https://raw.githubusercontent.com/powerhome/oss-guide/master/license_rules.yml
4
- - - :approve
5
- - bundler
6
- - :who: Carlos Palhares <carlos.palhares@powerhrg.com>
7
- :why: 'Bundler <https://rubygems.org/gems/bundler/versions/1.17.3> is licensed under MIT, but license_finder sometimes fails to resolve that with 1.17.3.'
8
- :versions: [1.17.3]
9
- :when: 2022-11-02 20:36:55.617698000 Z
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "concurrent-ruby"
4
-
5
- module CobraCommander
6
- module Executor
7
- # A threaded execution environment, limited by the number of given workers
8
- class Execution < Hash
9
- #
10
- # @param jobs [Array<#call>] array of job objects
11
- # @param workers [Integer] number of workers to process this execution
12
- # @see CobraCommander::Executor::Job
13
- def initialize(jobs, workers:)
14
- super()
15
-
16
- @executor = Concurrent::FixedThreadPool.new(workers, auto_terminate: true)
17
- merge! create_futures(jobs)
18
- end
19
-
20
- # Wait for all jobs to complete, returns a future with all execution futures
21
- # @return [Concurrent::Promises::Future]
22
- def wait
23
- Concurrent::Promises.zip_futures_on(@executor, *values)
24
- .tap(&:wait)
25
- end
26
-
27
- # The execution succeeds when all jobs succeeded
28
- # @return [Boolean]
29
- def success?
30
- values.all?(&:fulfilled?)
31
- end
32
-
33
- private
34
-
35
- def create_future(job)
36
- Concurrent::Promises.future_on(@executor, job, &:call).then do |result|
37
- status, output = result
38
- case status
39
- when :error then Concurrent::Promises.rejected_future(output)
40
- when :success, :skip then Concurrent::Promises.fulfilled_future(output)
41
- else
42
- Concurrent::Promises.fulfilled_future(result)
43
- end
44
- end.flat
45
- end
46
-
47
- def create_futures(jobs)
48
- jobs.to_h { |job| [job, create_future(job)] }
49
- end
50
- end
51
- end
52
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pastel"
4
- require "tty-prompt"
5
-
6
- module CobraCommander
7
- module Executor
8
- # Runs an interactive output printer
9
- class InteractivePrinter
10
- pastel = Pastel.new
11
- SUCCESS = "#{pastel.green('✔')} %s"
12
- ERROR = "#{pastel.red('✖')} %s"
13
- BYE = pastel.decorate("\n\n👋 Bye!", :white, :on_black, :bold).freeze
14
-
15
- def self.run(execution, output)
16
- new(execution).run(output)
17
- end
18
-
19
- def initialize(execution)
20
- @prompt = TTY::Prompt.new
21
- @execution = execution
22
- end
23
-
24
- def run(output)
25
- selected = nil
26
- loop do
27
- selected = @prompt.select("Print output?", options, default: options.key(selected))
28
- output.puts selected.fulfilled? ? selected.value : selected.reason
29
- end
30
- rescue TTY::Reader::InputInterrupt
31
- output.puts BYE
32
- end
33
-
34
- private
35
-
36
- def options
37
- @options ||= @execution.sort { |*args| sort_results(*args.flatten) }
38
- .reduce({}) do |options, (job, result)|
39
- template = result.rejected? ? ERROR : SUCCESS
40
- options.merge format(template, job.to_s) => result
41
- end
42
- end
43
-
44
- def sort_results(job_a, result_a, job_b, result_b)
45
- if result_a.rejected? == result_b.rejected?
46
- job_a.to_s <=> job_b.to_s
47
- else
48
- result_b.rejected? ? 1 : -1
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tty-command"
4
-
5
- module CobraCommander
6
- module Executor
7
- #
8
- # This is a helper module to help jobs return valid errors and success outputs.
9
- #
10
- # A CobraCommander::Executor::Job is actually any object responding to #call, and returning
11
- # valid error or success responses.
12
- #
13
- # An error is an array containint `:error` and the output (i.e.: [:error, "string output"]).
14
- # A success is either an array containint `:success` and the output, or just the output
15
- # (i.e.: [:error, "string output"] or just "string output").
16
- #
17
- module Job
18
- def skip(reason)
19
- [:skip, reason]
20
- end
21
-
22
- def error(output)
23
- [:error, output]
24
- end
25
-
26
- def success(output)
27
- [:success, output]
28
- end
29
-
30
- def run_script(script, path)
31
- result = isolate_bundle do
32
- TTY::Command.new(pty: true, printer: :null)
33
- .run!(script, chdir: path, err: :out)
34
- end
35
- return error(result.out) if result.failed?
36
-
37
- success(result.out)
38
- end
39
-
40
- private
41
-
42
- def isolate_bundle(&block)
43
- if Bundler.respond_to?(:with_unbundled_env)
44
- Bundler.with_unbundled_env(&block)
45
- else
46
- Bundler.with_clean_env(&block)
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CobraCommander
4
- module Executor
5
- # Prints the given CobraCommander::Executor::Context to [output] collection in markdown
6
- module MarkdownPrinter
7
- SUCCESS = "\n## ✔ %s\n"
8
- ERROR = "\n## ✖ %s\n"
9
- OUTPUT = "\n```\n\n%s\n```\n"
10
-
11
- def self.run(execution, output)
12
- execution.each do |job, result|
13
- template = result.fulfilled? ? SUCCESS : ERROR
14
-
15
- output.print format(template, job)
16
- output.print format(OUTPUT, result.fulfilled? ? result.value : result.reason)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pastel"
4
- require "tty-spinner"
5
-
6
- module CobraCommander
7
- module Executor
8
- class Spinners
9
- pastel = ::Pastel.new
10
- SPINNER_OPTIONS = {
11
- format: :bouncing,
12
- success_mark: pastel.green("[DONE]"),
13
- error_mark: pastel.red("[ERROR]"),
14
- }.freeze
15
-
16
- def self.start(execution, output:)
17
- new(execution, output: output).start
18
- end
19
-
20
- def initialize(execution, output:)
21
- @multi = TTY::Spinner::Multi.new(":spinner :task", output: output)
22
- @multi.top_spinner.update(task: "Running")
23
- execution.each { |job, result| register_spinner(job, result) }
24
- end
25
-
26
- def start
27
- @multi.auto_spin
28
- end
29
-
30
- private
31
-
32
- def register_spinner(job, result)
33
- @multi.register(":spinner #{job}", **SPINNER_OPTIONS) do |spinner|
34
- result.on_fulfillment!(spinner) { |_, spin| spin.success }
35
- .on_rejection!(spinner) { |_, spin| spin.error }
36
- end
37
- end
38
- end
39
- end
40
- end
data/mkdocs.yml DELETED
@@ -1,8 +0,0 @@
1
- site_name: Cobra Commander
2
- site_description: Cobra Commander Documentation
3
- repo_url: https://github.com/powerhome/cobra_commander
4
- nav:
5
- - "Home": "README.md"
6
- - "Changelog": "CHANGELOG.md"
7
- plugins:
8
- - techdocs-core