dry-stack 0.1.54 → 0.1.55

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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rparallel +83 -0
  3. data/lib/version.rb +1 -1
  4. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed1023e354efbaab3d973e49eddfea5263cecaa67f923b6033c9c29bc5a72b53
4
- data.tar.gz: 7736759f1f835f8943386f1a3f36493c54d2f36b99d2b11c6404e8488bf2668a
3
+ metadata.gz: 31af448879164bcafc2849deddb1a57ac32c879d8802c778a011ba63a042aac8
4
+ data.tar.gz: ea33f69f427432a01a44eee65bbfe47252abbc3ead5c41f81efe4dbd66119b45
5
5
  SHA512:
6
- metadata.gz: e289b884edf0fdafc68fc5e13aeffec712f5c7309937f692bf328f07d8bc9eed593de91619b87baf5289a9148f02c7aa87370fb596334b524ae110e21d9a6af1
7
- data.tar.gz: 13e7747378d96bd699361df4610e05f386a1a7503abece52b2233ba451236bb3b383db1e0e5267880a7d1c067f2a782181102c959546cb99c3d76c3c6c9eebef
6
+ metadata.gz: f1ceae1fc14e54826ca71d95499e994114f02729aa3701e899a720fd6266167458e9b99626103a9a9bee23a0d8c69633ae8a49741225f9c9a65678b325c1cc0e
7
+ data.tar.gz: 8e01abf90ecd9706ec7eba012adfe55eb73e5682563c1b5e6e55ac3d5bd7d65a3a52a6fd2b9b2c36d5ca52389d2f5fdab4d1e5aa84c70e93dd6cf0c2b293c885
data/bin/rparallel ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "open3"
5
+
6
+ Task = Struct.new(:number, :command, :started_at, :ended_at, :exit_code, keyword_init: true) do
7
+ def duration = ended_at - started_at
8
+
9
+ def status = exit_code.zero? ? "ok" : "failed"
10
+ end
11
+
12
+ class RParallel
13
+ def self.run(argv, stdin: $stdin, stdout: $stdout, stderr: $stderr)
14
+ return usage(stdout) if argv == ["--help"] || argv == ["-h"]
15
+
16
+ unless argv.empty?
17
+ stderr.puts "Unsupported arguments: #{argv.join(' ')}"
18
+ return 2
19
+ end
20
+
21
+ commands = stdin.each_line.map(&:strip).reject(&:empty?)
22
+ tasks = commands.each_with_index.map { |command, index| Task.new(number: index + 1, command:) }
23
+
24
+ tasks.map { |task| Thread.new { run_task(task, stdout, stderr) } }.each(&:join)
25
+ print_report(tasks, stdout)
26
+
27
+ tasks.all? { _1.exit_code.zero? } ? 0 : 1
28
+ end
29
+
30
+ def self.run_task(task, stdout, stderr)
31
+ task.started_at = monotonic_time
32
+
33
+ Open3.popen3("sh", "-c", task.command) do |child_stdin, child_stdout, child_stderr, wait_thread|
34
+ child_stdin.close
35
+ readers = [
36
+ Thread.new { IO.copy_stream(child_stdout, stdout) },
37
+ Thread.new { IO.copy_stream(child_stderr, stderr) }
38
+ ]
39
+ readers.each(&:join)
40
+ task.exit_code = wait_thread.value.exitstatus || 1
41
+ end
42
+ rescue StandardError => e
43
+ stderr.puts "Task #{task.number} failed to start: #{e.message}"
44
+ task.exit_code = 1
45
+ ensure
46
+ task.ended_at = monotonic_time
47
+ end
48
+
49
+ def self.print_report(tasks, stdout)
50
+ rows = tasks.map do |task|
51
+ [task.number.to_s, task.status, task.exit_code.to_s, format("%.3fs", task.duration), task.command]
52
+ end
53
+ widths = column_widths([["#", "status", "exit", "duration", "command"], *rows])
54
+
55
+ stdout.puts
56
+ stdout.puts "rparallel report"
57
+ stdout.puts format_row(["#", "status", "exit", "duration", "command"], widths)
58
+ rows.each { stdout.puts format_row(_1, widths) }
59
+ end
60
+
61
+ def self.column_widths(rows)
62
+ rows.transpose.map { _1.map(&:length).max }
63
+ end
64
+
65
+ def self.format_row(row, widths)
66
+ row.each_with_index.map do |value, index|
67
+ index == row.length - 1 ? value : value.ljust(widths[index])
68
+ end.join(" ")
69
+ end
70
+
71
+ def self.usage(stdout)
72
+ stdout.puts "Usage: rparallel < tasks.txt"
73
+ stdout.puts
74
+ stdout.puts "Reads non-empty stdin lines as shell tasks, runs them in parallel, then prints a report."
75
+ 0
76
+ end
77
+
78
+ def self.monotonic_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
79
+
80
+ private_class_method :run_task, :print_report, :column_widths, :format_row, :usage, :monotonic_time
81
+ end
82
+
83
+ exit RParallel.run(ARGV)
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  class Stack
3
- VERSION = '0.1.54'
3
+ VERSION = '0.1.55'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.54
4
+ version: 0.1.55
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artyom B
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-24 00:00:00.000000000 Z
11
+ date: 2026-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -98,10 +98,12 @@ description: ''
98
98
  email: author@email.address
99
99
  executables:
100
100
  - dry-stack
101
+ - rparallel
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
104
  files:
104
105
  - bin/dry-stack
106
+ - bin/rparallel
105
107
  - lib/dry-stack.rb
106
108
  - lib/dry-stack/apache_specific_md5.rb
107
109
  - lib/dry-stack/command_compose.rb