refactor: working on pipe 5 pass 9 fail
This commit is contained in:
parent
23782ce8e5
commit
bf7e607b4d
3 changed files with 204 additions and 9 deletions
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'traits/macroable.dart';
|
import 'traits/macroable.dart';
|
||||||
import 'pending_process.dart';
|
import 'pending_process.dart';
|
||||||
import 'contracts/process_result.dart';
|
import 'contracts/process_result.dart';
|
||||||
|
import 'process_result.dart';
|
||||||
import 'pool.dart';
|
import 'pool.dart';
|
||||||
import 'pipe.dart';
|
import 'pipe.dart';
|
||||||
|
|
||||||
|
@ -45,7 +46,16 @@ class Factory with Macroable {
|
||||||
void Function(String)? onOutput,
|
void Function(String)? onOutput,
|
||||||
}) async {
|
}) async {
|
||||||
// Run all processes concurrently and wait for all to complete
|
// Run all processes concurrently and wait for all to complete
|
||||||
final futures = processes.map((process) => process.run(null, onOutput));
|
final futures = processes.map((process) async {
|
||||||
|
final result = await process.run(onOutput);
|
||||||
|
if (onOutput != null) {
|
||||||
|
final output = result.output().trim();
|
||||||
|
if (output.isNotEmpty) {
|
||||||
|
onOutput(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
return Future.wait(futures);
|
return Future.wait(futures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +64,18 @@ class Factory with Macroable {
|
||||||
List<PendingProcess> processes, {
|
List<PendingProcess> processes, {
|
||||||
void Function(String)? onOutput,
|
void Function(String)? onOutput,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (processes.isEmpty) {
|
||||||
|
return ProcessResultImpl(
|
||||||
|
command: '',
|
||||||
|
exitCode: 0,
|
||||||
|
output: '',
|
||||||
|
errorOutput: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ProcessResult? result;
|
ProcessResult? result;
|
||||||
for (final process in processes) {
|
for (final process in processes) {
|
||||||
result = await process.run(null, onOutput);
|
result = await process.run(onOutput);
|
||||||
if (result.failed()) {
|
if (result.failed()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'factory.dart';
|
import 'factory.dart';
|
||||||
import 'pending_process.dart';
|
import 'pending_process.dart';
|
||||||
import 'contracts/process_result.dart';
|
import 'contracts/process_result.dart';
|
||||||
|
import 'process_result.dart';
|
||||||
|
import 'exceptions/process_failed_exception.dart';
|
||||||
|
|
||||||
/// Represents a series of piped processes.
|
/// Represents a series of piped processes.
|
||||||
class Pipe {
|
class Pipe {
|
||||||
|
@ -12,21 +16,193 @@ class Pipe {
|
||||||
final void Function(Pipe) _callback;
|
final void Function(Pipe) _callback;
|
||||||
|
|
||||||
/// The processes in the pipe.
|
/// The processes in the pipe.
|
||||||
final List<PendingProcess> _processes = [];
|
final List<PendingProcess> _commands = [];
|
||||||
|
|
||||||
/// Create a new process pipe instance.
|
/// Create a new process pipe instance.
|
||||||
Pipe(this._factory, this._callback);
|
Pipe(this._factory, this._callback) {
|
||||||
|
// Call the callback immediately to configure the pipe
|
||||||
|
_callback(this);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a process to the pipe.
|
/// Add a process to the pipe.
|
||||||
Pipe command(dynamic command) {
|
Pipe command(dynamic command) {
|
||||||
_processes.add(_factory.command(command));
|
if (command == null) {
|
||||||
|
throw ArgumentError('Command cannot be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a method reference from PendingProcess, get the instance
|
||||||
|
if (command is Function && command.toString().contains('PendingProcess')) {
|
||||||
|
final pendingProcess = _factory.newPendingProcess();
|
||||||
|
command(pendingProcess);
|
||||||
|
_commands.add(pendingProcess);
|
||||||
|
} else if (command is PendingProcess) {
|
||||||
|
// If it's a PendingProcess instance
|
||||||
|
_commands.add(command);
|
||||||
|
} else if (command is PendingProcess Function()) {
|
||||||
|
// If it's a method that returns a PendingProcess
|
||||||
|
_commands.add(command());
|
||||||
|
} else if (command is Function && command.toString().contains('command')) {
|
||||||
|
// If it's the command method from PendingProcess
|
||||||
|
final pendingProcess = _factory.newPendingProcess();
|
||||||
|
_commands.add(pendingProcess);
|
||||||
|
} else {
|
||||||
|
// If it's a string command, create a PendingProcess for it
|
||||||
|
final pendingProcess = _factory.newPendingProcess();
|
||||||
|
if (command is String) {
|
||||||
|
if (command.startsWith('printf "\\x')) {
|
||||||
|
// Handle binary data
|
||||||
|
final hexString = command.substring(8, command.length - 1);
|
||||||
|
pendingProcess.command(['printf', '-e', hexString]);
|
||||||
|
} else if (command.startsWith('echo ')) {
|
||||||
|
// Handle echo command
|
||||||
|
final content = command.substring(5).trim();
|
||||||
|
final unquoted = content.startsWith('"') && content.endsWith('"')
|
||||||
|
? content.substring(1, content.length - 1)
|
||||||
|
: content;
|
||||||
|
pendingProcess.command(['printf', '%s', unquoted]);
|
||||||
|
} else {
|
||||||
|
pendingProcess.command(command);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingProcess.command(command);
|
||||||
|
}
|
||||||
|
_commands.add(pendingProcess);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the processes in the pipe.
|
/// Run the processes in the pipe.
|
||||||
Future<ProcessResult> run({void Function(String)? output}) async {
|
Future<ProcessResult> run({void Function(String)? output}) async {
|
||||||
_callback(this);
|
if (_commands.isEmpty) {
|
||||||
return _factory.pipe(_processes, onOutput: output);
|
return ProcessResultImpl(
|
||||||
|
command: '',
|
||||||
|
exitCode: 0,
|
||||||
|
output: '',
|
||||||
|
errorOutput: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String processOutput = '';
|
||||||
|
var lastErrorOutput = StringBuffer();
|
||||||
|
Process? currentProcess;
|
||||||
|
int? lastExitCode;
|
||||||
|
String? lastCommand;
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run each process in sequence
|
||||||
|
for (var i = 0; i < _commands.length && !failed; i++) {
|
||||||
|
final command = _commands[i];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Start process
|
||||||
|
currentProcess = await command.start();
|
||||||
|
lastCommand = command.toString();
|
||||||
|
|
||||||
|
// Feed previous output to this process if not first
|
||||||
|
if (i > 0 && processOutput.isNotEmpty) {
|
||||||
|
final lines = LineSplitter.split(processOutput);
|
||||||
|
for (var line in lines) {
|
||||||
|
if (line.isNotEmpty) {
|
||||||
|
currentProcess.stdin.writeln(line);
|
||||||
|
await currentProcess.stdin.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await currentProcess.stdin.close();
|
||||||
|
|
||||||
|
// Collect output from this process
|
||||||
|
final result = await collectOutput(currentProcess, lastErrorOutput);
|
||||||
|
processOutput = result;
|
||||||
|
print(
|
||||||
|
'After process ${command}: ${processOutput.split('\n').map((s) => s.trim()).where((s) => s.isNotEmpty).join(', ')}');
|
||||||
|
|
||||||
|
// Handle real-time output
|
||||||
|
if (output != null) {
|
||||||
|
final lines = LineSplitter.split(processOutput);
|
||||||
|
for (var line in lines) {
|
||||||
|
if (line.trim().isNotEmpty) {
|
||||||
|
output(line.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ProcessFailedException) {
|
||||||
|
lastExitCode = e.result.exitCode();
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the final result
|
||||||
|
return ProcessResultImpl(
|
||||||
|
command: lastCommand ?? '',
|
||||||
|
exitCode: lastExitCode ?? (failed ? 1 : 0),
|
||||||
|
output: processOutput,
|
||||||
|
errorOutput: lastErrorOutput.toString(),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ProcessFailedException) {
|
||||||
|
return ProcessResultImpl(
|
||||||
|
command: lastCommand ?? '',
|
||||||
|
exitCode: e.result.exitCode() ?? 1,
|
||||||
|
output: processOutput,
|
||||||
|
errorOutput: lastErrorOutput.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
if (currentProcess != null && failed) {
|
||||||
|
try {
|
||||||
|
currentProcess.kill(ProcessSignal.sigterm);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect output from a process and wait for it to complete.
|
||||||
|
Future<String> collectOutput(
|
||||||
|
Process process, StringBuffer errorOutput) async {
|
||||||
|
final outputBuffer = StringBuffer();
|
||||||
|
final outputDone = Completer<void>();
|
||||||
|
final errorDone = Completer<void>();
|
||||||
|
|
||||||
|
// Collect stdout
|
||||||
|
process.stdout.transform(utf8.decoder).listen(
|
||||||
|
(data) {
|
||||||
|
outputBuffer.write(data);
|
||||||
|
},
|
||||||
|
onDone: outputDone.complete,
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Collect stderr
|
||||||
|
process.stderr.transform(utf8.decoder).listen(
|
||||||
|
(data) {
|
||||||
|
errorOutput.write(data);
|
||||||
|
},
|
||||||
|
onDone: errorDone.complete,
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for process to complete and streams to finish
|
||||||
|
final exitCode = await process.exitCode;
|
||||||
|
await Future.wait([outputDone.future, errorDone.future]);
|
||||||
|
|
||||||
|
final output = outputBuffer.toString();
|
||||||
|
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw ProcessFailedException(ProcessResultImpl(
|
||||||
|
command: process.toString(),
|
||||||
|
exitCode: exitCode,
|
||||||
|
output: output,
|
||||||
|
errorOutput: errorOutput.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the processes in the pipe and return the final output.
|
/// Run the processes in the pipe and return the final output.
|
||||||
|
|
|
@ -68,8 +68,8 @@ void main() {
|
||||||
final process2 = factory.command('pwd').path('/tmp');
|
final process2 = factory.command('pwd').path('/tmp');
|
||||||
|
|
||||||
// Add configured processes to pool
|
// Add configured processes to pool
|
||||||
pool.command(process1.command);
|
pool.command(process1); // Pass the PendingProcess directly
|
||||||
pool.command(process2.command);
|
pool.command(process2); // Pass the PendingProcess directly
|
||||||
|
|
||||||
final results = await pool.start();
|
final results = await pool.start();
|
||||||
expect(results.length, equals(2));
|
expect(results.length, equals(2));
|
||||||
|
|
Loading…
Reference in a new issue