blob: 434572e6877581a7346664f54bdb139a935a1531 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'dart:async';
/// An abstract class describing a workflow action.
abstract class WorkflowAction {}
/// [WaitForInputWorkflowAction] signals to the workflow engine that the current
/// step is waiting for user input.
class WaitForInputWorkflowAction extends WorkflowAction {}
/// [NavigateStepWorkflowAction] signals to the workflow engine that a
/// transition to a new step [nextStep] should happen. It is possible to send
/// state along by using the [payload].
class NavigateStepWorkflowAction<T> extends WorkflowAction {
WorkflowStep nextStep;
T payload;
NavigateStepWorkflowAction(this.nextStep, this.payload);
}
/// [BackWorkflowAction] signals to the workflow engine that it should pop the
/// current step and return focus to the previous step.
class BackWorkflowAction extends WorkflowAction {}
/// Base class for a single step in a larger [Workflow].
abstract class WorkflowStep<T> {
/// The [onShow] is called whenever the step receives focus from the workflow
/// engine. This happens both when transitioning and when going back. When
/// going back [payload] is null.
Future<WorkflowAction> onShow(T payload);
/// The [onLeave] is called on the current step, before the workflow engine
/// transitions to a new step. The transition can be cancelled by returning
/// true.
Future<bool> onLeave();
/// Input received by the workflow engine. The step should return what
/// workflow action to take after processing the input.
Future<WorkflowAction> input(String input);
}
/// [Workflow] is a class that makes it easy to create workflows. A workflow is
/// a collection of steps, with transitions between the steps to complete the
/// workflow. Viewed steps are kept track of by a stack, which makes it easy for
/// steps to transition back.
///
/// All access to navigation is hidden, thereby making steps unaware of the
/// workflow it is in. Navigation can only be changed by workflow actions.
class Workflow {
final List<WorkflowStep> _lastSteps = [];
WorkflowStep get currentStep {
return _lastSteps.length > 0 ? _lastSteps.last : null;
}
/// Start the workflow by providing the first step to show.
Future start(WorkflowStep firstStep) {
return _navigate(firstStep, null);
}
/// Handling the leaving of a step and asks the step if it is ok to leave.
Future _navigateLeave() async {
if (currentStep != null) {
var result = await currentStep.onLeave();
if (result == true) {
// Cancel the navigation. We should wait for input.
return _handleWorkflowAction(new WaitForInputWorkflowAction());
}
}
}
/// Handling the navigation to a step and adding the new step as the current.
Future _navigate<T>(WorkflowStep navigateTo, T payload) async {
await _navigateLeave();
_lastSteps.add(navigateTo);
await _handleWorkflowAction(await currentStep.onShow(payload));
}
/// Handling the workflow actions a feedback recursive loop.
Future _handleWorkflowAction(WorkflowAction action) async {
if (action is WaitForInputWorkflowAction) {
String input = stdin.readLineSync();
return _handleWorkflowAction(await currentStep.input(input));
} else if (action is NavigateStepWorkflowAction) {
return _navigate(action.nextStep, action.payload);
} else if (action is BackWorkflowAction) {
await _navigateLeave();
_lastSteps.removeLast();
var lastStep = _lastSteps.last;
while (_lastSteps.isNotEmpty && lastStep is ComputeStep) {
lastStep = _lastSteps.removeLast();
}
if (lastStep != null) {
return _handleWorkflowAction(await lastStep.onShow(null));
}
}
}
}
/// [ComputeStepPayload] is the payload for doing [ComputeStep].
class ComputeStepPayload {
final Future action;
final String message;
final WorkflowStep completedStep;
ComputeStepPayload(this.action, this.message, this.completedStep);
}
/// [ComputeStep] is similar to a loading screen, showing a dot every second
/// until the computation in the future [action] in the payload is completed.
/// When completed, it will ask for a transition by returning
/// [NavigateStepWorkflowAction].
class ComputeStep extends WorkflowStep<ComputeStepPayload> {
@override
Future<bool> onLeave() async {
// Do nothing.
return false;
}
@override
Future<WorkflowAction> onShow(ComputeStepPayload payload) async {
stdout.write(payload.message);
var timer = new Timer(new Duration(seconds: 1), () {
stdout.write(".");
});
var result = await payload.action;
timer.cancel();
stdout.write("\n");
return new NavigateStepWorkflowAction(payload.completedStep, result);
}
@override
Future<WorkflowAction> input(String input) {
// Do nothing.
return null;
}
}