ruby-progress 1.2.4 → 1.3.1
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 +4 -4
- data/CHANGELOG.md +33 -1
- data/Gemfile.lock +1 -1
- data/README.md +79 -134
- data/bin/prg +35 -0
- data/examples/daemon_job_example.sh +25 -0
- data/lib/ruby-progress/cli/fill_options.rb +22 -0
- data/lib/ruby-progress/cli/job_cli.rb +99 -0
- data/lib/ruby-progress/cli/ripple_cli.rb +66 -5
- data/lib/ruby-progress/cli/ripple_options.rb +10 -0
- data/lib/ruby-progress/cli/twirl_options.rb +12 -1
- data/lib/ruby-progress/cli/twirl_runner.rb +55 -2
- data/lib/ruby-progress/cli/twirl_spinner.rb +1 -0
- data/lib/ruby-progress/cli/worm_cli.rb +50 -16
- data/lib/ruby-progress/cli/worm_options.rb +20 -3
- data/lib/ruby-progress/cli/worm_runner.rb +37 -15
- data/lib/ruby-progress/daemon.rb +65 -0
- data/lib/ruby-progress/fill.rb +4 -0
- data/lib/ruby-progress/fill_cli.rb +30 -0
- data/lib/ruby-progress/output_capture.rb +136 -0
- data/lib/ruby-progress/ripple.rb +1 -0
- data/lib/ruby-progress/version.rb +6 -6
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f08082d9cebd17f7d3ece436ff76e4f4beb8f934d803719a6bdc3477fa8c97b
|
4
|
+
data.tar.gz: da85e2d2e2587c13f4c778094fb1dcd8e2aa832dd0cff34b947b90db0f4e6585
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3a816024fd6cc2aba00edd334c8b82e3783d6be3916bc36238172add46c695febcd80689ba49179b0d48a2175f983dca6b381e482c143e72f2bb0d80804b247
|
7
|
+
data.tar.gz: 56fe19077105ebf00cdae2c939eb761d0b37ae4f4cb82b439207c0562ed46cd97abd9105e7b6c0789868fa6087f5d646736b3ad0a58d3df6d09e5028add6d4bf
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# CHANGELOG
|
3
2
|
|
4
3
|
All notable changes to this project will be documented in this file.
|
@@ -6,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
6
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
7
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
8
7
|
|
8
|
+
## 1.3.0 - 2025-10-12
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- PTY-based output capture: `RubyProgress::OutputCapture` now allows running commands under a PTY, keeps a rolling buffer of the last N lines for live redraw, and optionally writes the full streamed output to a `log_path` file.
|
13
|
+
- File-based daemon job queue: `RubyProgress::Daemon.process_jobs` implements atomic job enqueue/claim/processing with `.processing.result` metadata files and processed-archive behavior.
|
14
|
+
- `prg job send` CLI helper: atomically writes jobs to daemon job dirs and supports `--wait` to poll for results, `--daemon-name`, `--pid-file`, `--stdin`, and `--timeout`.
|
15
|
+
- Integration of job processing into Ripple/Twirl/Worm daemon modes so a running daemon can accept and display job output without interrupting animations.
|
16
|
+
- CLI options for output handling: `--output-position` and `--output-lines` to reserve terminal rows for captured output.
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- Job result files now merge any Hash returned by the job handler into the `.processing.result` JSON (e.g., `exit_status`, `output`, `log_path`).
|
21
|
+
|
22
|
+
### Tests
|
23
|
+
|
24
|
+
- Added unit and integration tests covering job enqueueing, processing, and result persistence.
|
25
|
+
|
26
|
+
### Release notes
|
27
|
+
|
28
|
+
- Merge commit: 99d9c39 (squash-merge of feature/output-handling)
|
29
|
+
|
30
|
+
## 1.3.1 - 2025-10-12
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- `fill` subcommand: added `-c, --command COMMAND` so the determinate progress bar can run and capture command output like the other subcommands. This includes `--output-lines` and `--output-position` support for reserving terminal rows during capture.
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
- Bumped `FILL_VERSION` (patch) to reflect the new CLI behavior.
|
39
|
+
|
40
|
+
|
9
41
|
## 1.2.3 - 2025-10-11
|
10
42
|
|
11
43
|
### Added
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -8,6 +8,27 @@
|
|
8
8
|
|
9
9
|
This repository contains three different Ruby progress indicator projects: **Ripple**, **Worm**, and **Twirl**. All provide animated terminal progress indicators with different visual styles and features.
|
10
10
|
|
11
|
+
## Table of Contents
|
12
|
+
|
13
|
+
- [Unified Interface](#unified-interface)
|
14
|
+
- [Submitting jobs to a running daemon](#submitting-jobs-to-a-running-daemon)
|
15
|
+
- [Job result schema](#job-result-schema)
|
16
|
+
- [Example: start a daemon and send a job (simple)](#example-start-a-daemon-and-send-a-job-simple)
|
17
|
+
- [Ripple](#ripple)
|
18
|
+
- [Ripple Features](#ripple-features)
|
19
|
+
- [Ripple Usage](#ripple-usage)
|
20
|
+
- [Twirl](#twirl)
|
21
|
+
- [Worm](#worm)
|
22
|
+
- [Daemon mode (background indicator)](#daemon-mode-background-indicator)
|
23
|
+
- [Requirements](#requirements)
|
24
|
+
- [Installation](#installation)
|
25
|
+
- [Universal Utilities](#universal-utilities)
|
26
|
+
- [Terminal Control](#terminal-control)
|
27
|
+
- [Completion Messages](#completion-messages)
|
28
|
+
- [Development](#development)
|
29
|
+
- [Contributing](#contributing)
|
30
|
+
- [License](#license)
|
31
|
+
|
11
32
|
## Unified Interface
|
12
33
|
|
13
34
|
The gem provides a unified `prg` command that supports all progress indicators through subcommands:
|
@@ -30,12 +51,12 @@ prg twirl --message "Working..." --style dots --speed fast
|
|
30
51
|
|
31
52
|
# Run fill directly (delegates to prg)
|
32
53
|
fill --report --percent 50
|
33
|
-
```
|
34
54
|
|
35
55
|
### With command execution
|
36
56
|
prg worm --command "sleep 5" --success "Completed!" --error "Failed!" --checkmark
|
37
57
|
prg ripple "Building..." --command "make build" --success "Build complete!" --stdout
|
38
58
|
prg twirl --command "npm install" --message "Installing packages" --style arc
|
59
|
+
prg fill --command "sleep 5" --success "Done!" --checkmark
|
39
60
|
|
40
61
|
### With start/end character decoration using --ends
|
41
62
|
prg ripple "Loading data" --ends "[]" --style rainbow
|
@@ -49,158 +70,73 @@ prg worm --message "Magic" --ends "🎯🎪" --style "custom=🟦🟨🟥"
|
|
49
70
|
### Global Options
|
50
71
|
|
51
72
|
- `prg --help` - Show main help
|
52
|
-
- `prg --version` - Show version info
|
53
|
-
- `prg --list-styles` - Show all available styles for all subcommands
|
54
|
-
- `prg <subcommand> --help` - Show specific subcommand help
|
55
73
|
|
56
|
-
|
74
|
+
Notes:
|
57
75
|
|
58
|
-
-
|
59
|
-
- `--
|
60
|
-
-
|
61
|
-
- `--
|
62
|
-
- `--error MESSAGE` - Error message on failure
|
63
|
-
- `--checkmark` - Show checkmarks (✅ success, 🛑 failure)
|
64
|
-
- `--stdout` - Output command results to STDOUT
|
65
|
-
- `--ends CHARS` - Start/end characters (even number of chars, split in half)
|
76
|
+
- The CLI detaches itself (double-fork); do not append `&`. This prevents shell job notifications like “job … has ended.” The command returns immediately.
|
77
|
+
- `--stop-success` and `--stop-error` are mutually exclusive; whichever you provide determines the success state and icon if `--stop-checkmark` is set.
|
78
|
+
- The indicator clears its line on shutdown and prints the final message to STDOUT.
|
79
|
+
- `--stop-pid` is still supported for backward compatibility, but `--stop [--pid-file FILE]` is preferred.
|
66
80
|
|
67
|
-
###
|
81
|
+
### Submitting jobs to a running daemon
|
68
82
|
|
69
|
-
|
83
|
+
When running a long-lived daemon (for example `prg worm --daemon`), you can submit additional commands to run and have their output displayed without disrupting the animation using the `prg job send` helper.
|
84
|
+
|
85
|
+
Basic usage:
|
70
86
|
|
71
87
|
```bash
|
72
|
-
|
73
|
-
prg
|
88
|
+
# Enqueue a command to the default daemon PID
|
89
|
+
prg job send --command "./deploy-step.sh"
|
74
90
|
|
75
|
-
|
76
|
-
prg
|
91
|
+
# Enqueue to a named daemon (creates /tmp/ruby-progress/<name>.pid)
|
92
|
+
prg job send --daemon-name mytask --command "rsync -av ./dist/ user@host:/srv/app"
|
77
93
|
|
78
|
-
|
94
|
+
# Read command from stdin (useful in scripts)
|
95
|
+
echo "bundle exec rake db:migrate" | prg job send --stdin --daemon-name mytask
|
79
96
|
|
80
|
-
|
81
|
-
prg
|
97
|
+
# Wait for the job result and print the job result JSON (default timeout 10s)
|
98
|
+
prg job send --daemon-name mytask --command "./deploy-step.sh" --wait --timeout 30
|
99
|
+
```
|
82
100
|
|
83
|
-
|
84
|
-
prg worm --stop-id mytask --stop-success "Task complete!" --stop-checkmark
|
101
|
+
Behavior and file layout:
|
85
102
|
|
86
|
-
|
87
|
-
|
103
|
+
- Jobs are written as JSON files into the daemon's job directory, which is derived from the daemon PID file. For example, a PID file `/tmp/ruby-progress/mytask.pid` maps to the job directory `/tmp/ruby-progress/mytask.jobs`.
|
104
|
+
- The CLI writes the job atomically by first writing a `*.json.tmp` temporary file and then renaming it to `*.json`.
|
105
|
+
- The daemon's job processor claims jobs atomically by renaming the job file to `*.processing`, writes a `*.processing.result` JSON file when finished, and moves processed jobs to `processed-*`.
|
88
106
|
|
89
|
-
|
90
|
-
prg worm --status
|
91
|
-
prg worm --status-id mytask
|
107
|
+
This mechanism allows you to submit many commands to a single running indicator and have their output shown in reserved terminal rows while the animation continues.
|
92
108
|
|
93
|
-
|
94
|
-
prg worm --daemon --pid-file /tmp/custom-progress.pid
|
95
|
-
prg worm --status --pid-file /tmp/custom-progress.pid
|
96
|
-
prg worm --stop-success "Complete" --pid-file /tmp/custom-progress.pid
|
97
|
-
```
|
109
|
+
## Job result schema
|
98
110
|
|
99
|
-
|
111
|
+
When a job is processed the daemon writes a small JSON result file next to the claimed job with the suffix `.processing.result` containing at least these keys:
|
100
112
|
|
101
|
-
-
|
102
|
-
-
|
103
|
-
-
|
104
|
-
- `--stop-pid` is still supported for backward compatibility, but `--stop [--pid-file FILE]` is preferred.
|
105
|
-
o- [Ruby Progress Indicators](#ruby-progress-indicators)
|
106
|
-
- [Unified Interface](#unified-interface)
|
107
|
-
- [With command execution](#with-command-execution)
|
108
|
-
- [With start/end character decoration using --ends](#with-startend-character-decoration-using-ends)
|
109
|
-
- [Complex --ends patterns with emojis](#complex-ends-patterns-with-emojis)
|
110
|
-
- [Start in background (uses default PID file)](#start-in-background-uses-default-pid-file)
|
111
|
-
- [Start with a custom name (creates /tmp/ruby-progress/NAME.pid)](#start-with-a-custom-name-creates-tmpruby-progressnamepid)
|
112
|
-
- [... run your tasks ...](#-run-your-tasks-)
|
113
|
-
- [Stop with a success message and checkmark (--stop-success implies --stop)](#stop-with-a-success-message-and-checkmark-stop-success-implies-stop)
|
114
|
-
- [Stop a named daemon (--stop-id implies --stop)](#stop-a-named-daemon-stop-id-implies-stop)
|
115
|
-
- [Or stop with an error message and checkmark](#or-stop-with-an-error-message-and-checkmark)
|
116
|
-
- [Check status at any time](#check-status-at-any-time)
|
117
|
-
- [Use a completely custom PID file path](#use-a-completely-custom-pid-file-path)
|
118
|
-
- [Basic text animation](#basic-text-animation)
|
119
|
-
- [With style options](#with-style-options)
|
120
|
-
- [Multiple styles combined](#multiple-styles-combined)
|
121
|
-
- [Case transformation mode](#case-transformation-mode)
|
122
|
-
- [Run a command with progress animation](#run-a-command-with-progress-animation)
|
123
|
-
- [Simple progress block](#simple-progress-block)
|
124
|
-
- [With options](#with-options)
|
125
|
-
- [Basic spinner animation](#basic-spinner-animation)
|
126
|
-
- [With command execution](#with-command-execution)
|
127
|
-
- [Different spinner styles](#different-spinner-styles)
|
128
|
-
- [With success/error handling](#with-successerror-handling)
|
129
|
-
- [Daemon mode for background tasks](#daemon-mode-for-background-tasks)
|
130
|
-
- [... do other work ...](#-do-other-work-)
|
131
|
-
- [Run indefinitely without a command (like ripple)](#run-indefinitely-without-a-command-like-ripple)
|
132
|
-
- [Run a command with progress animation](#run-a-command-with-progress-animation)
|
133
|
-
- [Customize the animation](#customize-the-animation)
|
134
|
-
- [With custom error handling](#with-custom-error-handling)
|
135
|
-
- [With checkmarks for visual feedback](#with-checkmarks-for-visual-feedback)
|
136
|
-
- [Control animation direction (forward-only or bidirectional)](#control-animation-direction-forward-only-or-bidirectional)
|
137
|
-
- [Create custom animations with 3-character patterns](#create-custom-animations-with-3-character-patterns)
|
138
|
-
- [Add start/end characters around the animation](#add-startend-characters-around-the-animation)
|
139
|
-
- [Capture and display command output](#capture-and-display-command-output)
|
140
|
-
- [Combine checkmarks and stdout output](#combine-checkmarks-and-stdout-output)
|
141
|
-
- [Start in the background (default PID file: /tmp/ruby-progress/progress.pid)](#start-in-the-background-default-pid-file-tmpruby-progressprogresspid)
|
142
|
-
- [... run your tasks ...](#-run-your-tasks-)
|
143
|
-
- [Stop using the default PID file](#stop-using-the-default-pid-file)
|
144
|
-
- [Use a custom PID file](#use-a-custom-pid-file)
|
145
|
-
- [Stop using the matching custom PID file](#stop-using-the-matching-custom-pid-file)
|
146
|
-
- [Create and run animation with a block](#create-and-run-animation-with-a-block)
|
147
|
-
- [Your work here](#your-work-here)
|
148
|
-
- [With custom style and forward direction](#with-custom-style-and-forward-direction)
|
149
|
-
- [Or run with a command](#or-run-with-a-command)
|
150
|
-
- [ASCII characters](#ascii-characters)
|
151
|
-
- [Unicode characters](#unicode-characters)
|
152
|
-
- [Emojis (supports multi-byte characters)](#emojis-supports-multi-byte-characters)
|
153
|
-
- [Mixed ASCII and emoji](#mixed-ascii-and-emoji)
|
154
|
-
- [Cursor control](#cursor-control)
|
155
|
-
- [Basic completion message](#basic-completion-message)
|
156
|
-
- [With success/failure indication and checkmarks](#with-successfailure-indication-and-checkmarks)
|
157
|
-
- [Clear line and display completion (useful for replacing progress indicators)](#clear-line-and-display-completion-useful-for-replacing-progress-indicators)
|
113
|
+
- `id` - the job id (string)
|
114
|
+
- `status` - `"done"` or `"error"`
|
115
|
+
- `time` - epoch seconds when the job finished (integer)
|
158
116
|
|
159
|
-
|
117
|
+
Depending on the job handler, additional keys may be present:
|
118
|
+
|
119
|
+
- `exit_status` - the numeric process exit status (integer or nil if unknown)
|
120
|
+
- `output` - a string with the last captured lines of output (if available)
|
121
|
+
- `error` - an error message when `status` is `error`
|
160
122
|
|
161
|
-
|
162
|
-
- [Unified Interface](#unified-interface)
|
163
|
-
- [With command execution](#with-command-execution)
|
164
|
-
- [With start/end character decoration using --ends](#with-startend-character-decoration-using---ends)
|
165
|
-
- [Complex --ends patterns with emojis](#complex---ends-patterns-with-emojis)
|
166
|
-
- [Table of Contents](#table-of-contents)
|
167
|
-
- [Ripple](#ripple)
|
168
|
-
- [Ripple Features](#ripple-features)
|
169
|
-
- [Ripple Usage](#ripple-usage)
|
170
|
-
- [Ripple CLI examples](#ripple-cli-examples)
|
171
|
-
- [Ripple Command Line Options](#ripple-command-line-options)
|
172
|
-
- [Ripple Library Usage](#ripple-library-usage)
|
173
|
-
- [Twirl](#twirl)
|
174
|
-
- [Twirl Features](#twirl-features)
|
175
|
-
- [Twirl Usage](#twirl-usage)
|
176
|
-
- [Command Line](#command-line)
|
177
|
-
- [Twirl Command Line Options](#twirl-command-line-options)
|
178
|
-
- [Available Spinner Styles](#available-spinner-styles)
|
179
|
-
- [Worm](#worm)
|
180
|
-
- [Worm Features](#worm-features)
|
181
|
-
- [Worm Usage](#worm-usage)
|
182
|
-
- [Command Line](#command-line-1)
|
183
|
-
- [Daemon mode (background indicator)](#daemon-mode-background-indicator)
|
184
|
-
- [Worm Command Line Options](#worm-command-line-options)
|
185
|
-
- [Worm Library Usage](#worm-library-usage)
|
186
|
-
- [Animation Styles](#animation-styles)
|
187
|
-
- [Circles](#circles)
|
188
|
-
- [Blocks](#blocks)
|
189
|
-
- [Geometric](#geometric)
|
190
|
-
- [Custom Styles](#custom-styles)
|
191
|
-
- [Direction Control](#direction-control)
|
192
|
-
- [Requirements](#requirements)
|
193
|
-
- [Installation](#installation)
|
194
|
-
- [As a Gem (Recommended)](#as-a-gem-recommended)
|
195
|
-
- [From Source](#from-source)
|
196
|
-
- [Development](#development)
|
197
|
-
- [Universal Utilities](#universal-utilities)
|
198
|
-
- [Terminal Control](#terminal-control)
|
199
|
-
- [Completion Messages](#completion-messages)
|
200
|
-
- [Contributing](#contributing)
|
201
|
-
- [License](#license)
|
123
|
+
Example:
|
202
124
|
|
125
|
+
```json
|
126
|
+
{
|
127
|
+
"id": "8a1f6c1e-4b7a-4f2c-b0a8-9e9f1c2f1a2b",
|
128
|
+
"status": "done",
|
129
|
+
"time": 1634044800,
|
130
|
+
"exit_status": 0,
|
131
|
+
"output": "Step 1 completed\nStep 2 completed"
|
132
|
+
}
|
133
|
+
```
|
134
|
+
|
135
|
+
This file is intended for short messages and small captured output snippets (the CLI captures the last N lines). If you need larger logs, write them to a persistent file from the command itself and include a reference in the job metadata.
|
203
136
|
|
137
|
+
## Example: start a daemon and send a job (simple)
|
138
|
+
|
139
|
+
Below is an example script that demonstrates starting a worm daemon, sending a job, waiting for the result, and stopping the daemon.
|
204
140
|
---
|
205
141
|
|
206
142
|
## Ripple
|
@@ -407,6 +343,15 @@ prg worm --message "Emoji ends" --ends "🎯🎪" --style "custom=🟦🟨🟥"
|
|
407
343
|
### Capture and display command output
|
408
344
|
prg worm --command "git status" --message "Checking status" --stdout
|
409
345
|
|
346
|
+
You can reserve terminal rows for captured command output so the animation doesn't interleave with the script output. Use:
|
347
|
+
|
348
|
+
- `--output-position POSITION` — `above` (default) or `below` the animation
|
349
|
+
- `--output-lines N` — how many terminal rows to reserve for captured output (default: 3)
|
350
|
+
|
351
|
+
Examples:
|
352
|
+
|
353
|
+
prg worm --command "git status" --stdout --output-position above --output-lines 4
|
354
|
+
|
410
355
|
### Combine checkmarks and stdout output
|
411
356
|
prg worm --command "echo 'Build output'" --success "Build complete!" --checkmark --stdout
|
412
357
|
```
|
data/bin/prg
CHANGED
@@ -7,6 +7,7 @@ require 'optparse'
|
|
7
7
|
require 'json'
|
8
8
|
require 'English'
|
9
9
|
|
10
|
+
require_relative '../lib/ruby-progress/cli/job_cli'
|
10
11
|
# Load extracted per-subcommand CLI modules
|
11
12
|
require_relative '../lib/ruby-progress/cli/ripple_cli'
|
12
13
|
require_relative '../lib/ruby-progress/cli/worm_cli'
|
@@ -20,6 +21,18 @@ module PrgCLI
|
|
20
21
|
exit 1
|
21
22
|
end
|
22
23
|
|
24
|
+
# Handle `job` subcommands early
|
25
|
+
if ARGV[0] && ARGV[0].downcase == 'job'
|
26
|
+
ARGV.shift
|
27
|
+
sub = ARGV.shift
|
28
|
+
case sub
|
29
|
+
when 'send'
|
30
|
+
JobCLI.send(ARGV)
|
31
|
+
else
|
32
|
+
puts 'job subcommands: send'
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
end
|
23
36
|
# Early scan: detect --ends flag and validate its argument before dispatching
|
24
37
|
if (i = ARGV.index('--ends')) && ARGV[i + 1]
|
25
38
|
ends_val = ARGV[i + 1]
|
@@ -113,6 +126,28 @@ module PrgCLI
|
|
113
126
|
puts '== fill =='
|
114
127
|
end
|
115
128
|
|
129
|
+
# Detach the current process into a background daemon. Uses Process.daemon
|
130
|
+
# when available, otherwise falls back to a basic fork/exit helper. This is
|
131
|
+
# intentionally simple for the test environment.
|
132
|
+
def self.daemonize
|
133
|
+
if Process.respond_to?(:daemon)
|
134
|
+
Process.daemon(true)
|
135
|
+
else
|
136
|
+
pid = fork
|
137
|
+
if pid
|
138
|
+
# parent exits so child can continue as daemon
|
139
|
+
exit(0)
|
140
|
+
else
|
141
|
+
# child: detach from controlling terminal
|
142
|
+
begin
|
143
|
+
Process.setsid
|
144
|
+
rescue StandardError
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
116
151
|
# Attempt to stop processes for the given subcommand. Return true if any
|
117
152
|
# process was signaled/stopped; false otherwise. Keep quiet on missing
|
118
153
|
# processes to satisfy integration tests.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env sh
|
2
|
+
# Example: start a worm daemon, send a job, wait for result, then stop.
|
3
|
+
# This script assumes you're running from the project root and have a working
|
4
|
+
# `bin/prg` script in the repository.
|
5
|
+
|
6
|
+
set -eu
|
7
|
+
|
8
|
+
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
9
|
+
PRG_BIN="$PROJECT_ROOT/bin/prg"
|
10
|
+
|
11
|
+
echo "Starting worm daemon (named 'example')..."
|
12
|
+
# prg detaches in daemon mode so no & needed
|
13
|
+
$PRG_BIN worm --daemon-as example --message "Example daemon"
|
14
|
+
|
15
|
+
sleep 0.2
|
16
|
+
|
17
|
+
echo "Sending job and waiting for result..."
|
18
|
+
$PRG_BIN job send --daemon-name example --command "echo hello; sleep 0.1" --wait --timeout 10
|
19
|
+
|
20
|
+
sleep 0.1
|
21
|
+
|
22
|
+
echo "Stopping daemon with success message..."
|
23
|
+
$PRG_BIN worm --stop-success "Example finished" --stop-checkmark --daemon-name example
|
24
|
+
|
25
|
+
echo "Done."
|
@@ -23,6 +23,8 @@ module RubyProgress
|
|
23
23
|
stop: false,
|
24
24
|
status: false,
|
25
25
|
current: false,
|
26
|
+
output_position: :above,
|
27
|
+
output_lines: 3,
|
26
28
|
report: false
|
27
29
|
}
|
28
30
|
|
@@ -44,6 +46,20 @@ module RubyProgress
|
|
44
46
|
options[:ends] = chars
|
45
47
|
end
|
46
48
|
|
49
|
+
opts.separator 'Output capture:'
|
50
|
+
|
51
|
+
opts.on('-c', '--command COMMAND', 'Command to run and capture output (optional)') do |cmd|
|
52
|
+
options[:command] = cmd
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on('--output-position POSITION', 'Position to render captured output: above or below (default: above)') do |pos|
|
56
|
+
options[:output_position] = pos.to_sym
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on('--output-lines N', Integer, 'Number of output lines to reserve for captured output (default: 3)') do |n|
|
60
|
+
options[:output_lines] = n
|
61
|
+
end
|
62
|
+
|
47
63
|
opts.separator ''
|
48
64
|
opts.separator 'Progress Control:'
|
49
65
|
|
@@ -173,6 +189,12 @@ module RubyProgress
|
|
173
189
|
opts.on('--error MESSAGE', 'Error message to display on cancellation')
|
174
190
|
opts.on('--checkmark', 'Show checkmarks (✅ success, 🛑 failure)')
|
175
191
|
|
192
|
+
opts.separator ''
|
193
|
+
opts.separator ''
|
194
|
+
opts.separator 'Output capture:'
|
195
|
+
opts.on('--output-position POSITION', 'Position to render captured output: above or below (default: above)')
|
196
|
+
opts.on('--output-lines N', Integer, 'Number of output lines to reserve for captured output (default: 3)')
|
197
|
+
|
176
198
|
opts.separator ''
|
177
199
|
opts.separator 'Daemon Mode:'
|
178
200
|
opts.on('--daemon', 'Run in background daemon mode')
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# CLI: prg job
|
4
|
+
|
5
|
+
# Provides the `prg job send` helper to enqueue commands into a daemon's
|
6
|
+
# job directory. This file contains a minimal implementation used by tests.
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
require 'json'
|
10
|
+
require 'securerandom'
|
11
|
+
require 'fileutils'
|
12
|
+
require_relative '../daemon'
|
13
|
+
|
14
|
+
# Job CLI helpers
|
15
|
+
#
|
16
|
+
# Exposed as `prg job send`.
|
17
|
+
module JobCLI
|
18
|
+
# JobCLI
|
19
|
+
#
|
20
|
+
# Small CLI module that exposes `prg job send` for enqueuing jobs into the
|
21
|
+
# daemon job directory. This is intentionally minimal: it writes a single
|
22
|
+
# JSON file atomically and optionally waits for a result file created by
|
23
|
+
# the daemon's job processor.
|
24
|
+
# Simple CLI for submitting jobs to a running daemon job directory.
|
25
|
+
# Usage: prg job send --pid-file /tmp/... --command "echo hi" [--wait]
|
26
|
+
class Options
|
27
|
+
def self.parse(argv)
|
28
|
+
options = { wait: false }
|
29
|
+
opt = OptionParser.new do |o|
|
30
|
+
o.banner = 'Usage: prg job send [options]'
|
31
|
+
o.on('--pid-file PATH', 'Path to daemon pid file') { |v| options[:pid_file] = v }
|
32
|
+
o.on('--daemon-name NAME', 'Daemon name (maps to /tmp/ruby-progress/NAME.pid)') { |v| options[:daemon_name] = v }
|
33
|
+
o.on('--command CMD', 'Command to run') { |v| options[:command] = v }
|
34
|
+
o.on('--stdin', 'Read command from stdin (overrides --command)') { options[:stdin] = true }
|
35
|
+
o.on('--wait', 'Wait for result file and print it') { options[:wait] = true }
|
36
|
+
o.on('--timeout SECONDS', Integer, 'Timeout seconds for wait') { |v| options[:timeout] = v }
|
37
|
+
end
|
38
|
+
|
39
|
+
rest = opt.parse(argv)
|
40
|
+
options[:command] ||= rest.join(' ') unless rest.empty?
|
41
|
+
options
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.send(argv = ARGV)
|
46
|
+
opts = Options.parse(argv)
|
47
|
+
|
48
|
+
# Resolve pid file
|
49
|
+
pid_file = if opts[:pid_file]
|
50
|
+
opts[:pid_file]
|
51
|
+
elsif opts[:daemon_name]
|
52
|
+
"/tmp/ruby-progress/#{opts[:daemon_name]}.pid"
|
53
|
+
else
|
54
|
+
RubyProgress::Daemon.default_pid_file
|
55
|
+
end
|
56
|
+
|
57
|
+
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
58
|
+
FileUtils.mkdir_p(job_dir)
|
59
|
+
|
60
|
+
cmd = if opts[:stdin]
|
61
|
+
$stdin.read
|
62
|
+
else
|
63
|
+
opts[:command]
|
64
|
+
end
|
65
|
+
|
66
|
+
unless cmd && !cmd.strip.empty?
|
67
|
+
warn 'No command specified. Use --command or --stdin.'
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
job_id = SecureRandom.uuid
|
72
|
+
tmp = File.join(job_dir, "#{job_id}.json.tmp")
|
73
|
+
final = File.join(job_dir, "#{job_id}.json")
|
74
|
+
|
75
|
+
payload = { 'id' => job_id, 'command' => cmd }
|
76
|
+
|
77
|
+
File.write(tmp, JSON.dump(payload))
|
78
|
+
FileUtils.mv(tmp, final)
|
79
|
+
|
80
|
+
if opts[:wait]
|
81
|
+
timeout = opts[:timeout] || 10
|
82
|
+
start = Time.now
|
83
|
+
result_path = "#{final}.processing.result"
|
84
|
+
loop do
|
85
|
+
if File.exist?(result_path)
|
86
|
+
puts File.read(result_path)
|
87
|
+
break
|
88
|
+
end
|
89
|
+
if Time.now - start > timeout
|
90
|
+
warn 'Timed out waiting for result'
|
91
|
+
exit 2
|
92
|
+
end
|
93
|
+
sleep 0.1
|
94
|
+
end
|
95
|
+
else
|
96
|
+
puts job_id
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
require 'securerandom'
|
4
6
|
require_relative 'ripple_options'
|
7
|
+
require_relative '../output_capture'
|
5
8
|
|
6
9
|
# Enhanced Ripple CLI with unified flags (extracted from bin/prg)
|
7
10
|
module RippleCLI
|
@@ -60,13 +63,27 @@ module RippleCLI
|
|
60
63
|
end
|
61
64
|
|
62
65
|
def self.run_with_command(text, options)
|
63
|
-
|
64
|
-
|
66
|
+
if $stdout.tty? && options[:output] == :stdout
|
67
|
+
oc = RubyProgress::OutputCapture.new(command: options[:command], lines: options[:output_lines] || 3, position: options[:output_position] || :above)
|
68
|
+
oc.start
|
69
|
+
|
70
|
+
# Create rippler and attach output capture so redraw occurs each frame
|
71
|
+
rippler = RubyProgress::Ripple.new(text, options)
|
72
|
+
rippler.instance_variable_set(:@output_capture, oc)
|
73
|
+
|
74
|
+
thread = Thread.new { loop { rippler.advance } }
|
75
|
+
oc.wait
|
76
|
+
thread.kill
|
77
|
+
|
78
|
+
captured_lines = oc.lines
|
79
|
+
captured_output = captured_lines.join("\n")
|
80
|
+
success = true
|
81
|
+
else
|
82
|
+
# Fallback to legacy capture (non-interactive / CI)
|
65
83
|
captured_output = `#{options[:command]} 2>&1`
|
84
|
+
success = $CHILD_STATUS.success?
|
66
85
|
end
|
67
86
|
|
68
|
-
success = $CHILD_STATUS.success?
|
69
|
-
|
70
87
|
puts captured_output if options[:output] == :stdout
|
71
88
|
if options[:success_message] || options[:complete_checkmark]
|
72
89
|
message = success ? options[:success_message] : options[:fail_message] || options[:success_message]
|
@@ -90,7 +107,6 @@ module RippleCLI
|
|
90
107
|
pid_file = options[:pid_file] || RubyProgress::Daemon.default_pid_file
|
91
108
|
FileUtils.mkdir_p(File.dirname(pid_file))
|
92
109
|
File.write(pid_file, Process.pid.to_s)
|
93
|
-
|
94
110
|
begin
|
95
111
|
# For Ripple, re-use the existing animation loop via a simple loop
|
96
112
|
RubyProgress::Utils.hide_cursor
|
@@ -102,6 +118,9 @@ module RippleCLI
|
|
102
118
|
Signal.trap('TERM') { stop_requested = true }
|
103
119
|
Signal.trap('HUP') { stop_requested = true }
|
104
120
|
|
121
|
+
job_dir = RubyProgress::Daemon.job_dir_for_pid(pid_file)
|
122
|
+
job_thread = Thread.new { process_daemon_jobs_for_rippler(job_dir, rippler, options) }
|
123
|
+
|
105
124
|
rippler.advance until stop_requested
|
106
125
|
ensure
|
107
126
|
RubyProgress::Utils.clear_line
|
@@ -142,9 +161,51 @@ module RippleCLI
|
|
142
161
|
end
|
143
162
|
end
|
144
163
|
|
164
|
+
# stop job thread and cleanup
|
165
|
+
job_thread&.kill
|
145
166
|
FileUtils.rm_f(pid_file)
|
146
167
|
end
|
147
168
|
end
|
148
169
|
|
170
|
+
def self.process_daemon_jobs_for_rippler(job_dir, rippler, options)
|
171
|
+
RubyProgress::Daemon.process_jobs(job_dir) do |job|
|
172
|
+
jid = job['id'] || SecureRandom.uuid
|
173
|
+
log_path = begin
|
174
|
+
File.join(File.dirname(job_dir), "#{jid}.log")
|
175
|
+
rescue StandardError
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
oc = RubyProgress::OutputCapture.new(
|
180
|
+
command: job['command'],
|
181
|
+
lines: options[:output_lines] || 3,
|
182
|
+
position: options[:output_position] || :above,
|
183
|
+
log_path: log_path
|
184
|
+
)
|
185
|
+
oc.start
|
186
|
+
|
187
|
+
rippler.instance_variable_set(:@output_capture, oc)
|
188
|
+
oc.wait
|
189
|
+
captured = oc.lines.join("\n")
|
190
|
+
exit_status = oc.exit_status
|
191
|
+
rippler.instance_variable_set(:@output_capture, nil)
|
192
|
+
|
193
|
+
success = exit_status.to_i.zero?
|
194
|
+
if job['message']
|
195
|
+
RubyProgress::Utils.display_completion(
|
196
|
+
job['message'],
|
197
|
+
success: success,
|
198
|
+
show_checkmark: job['checkmark'] || false,
|
199
|
+
output_stream: :stdout
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
{ 'exit_status' => exit_status, 'output' => captured, 'log_path' => log_path }
|
204
|
+
rescue StandardError
|
205
|
+
# ignore per-job errors; process_jobs will write result
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
149
210
|
# Options parsing moved to ripple_options.rb
|
150
211
|
end
|