diff --git a/build.gradle b/build.gradle
index 95e377018b..358b8e6f3d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -96,6 +96,7 @@ run {
tasks.compileJava.mustRunAfter(zipReport)
args System.getProperty('args', '').split()
systemProperty "version", getRepoSenseVersion()
+ standardInput = System.in
}
checkstyle {
diff --git a/docs/_markbind/layouts/ug-sitenav.md b/docs/_markbind/layouts/ug-sitenav.md
index 5622340ed7..7da35f4339 100644
--- a/docs/_markbind/layouts/ug-sitenav.md
+++ b/docs/_markbind/layouts/ug-sitenav.md
@@ -8,6 +8,7 @@
{level: 2, name: "CLI syntax reference", link: "ug/cli.html"},
{level: 2, name: "Config files format", link: "ug/configFiles.html"},
{level: 2, name: "Using `@@author` tags", link: "ug/usingAuthorTags.html"},
+ {level: 2, name: "Using `--init` flag", link: "ug/usingInit.html"},
{level: 2, name: "RepoSense with Netlify", link: "ug/withNetlify.html"},
{level: 2, name: "RepoSense with GitHub Actions", link: "ug/withGithubActions.html"},
{level: 2, name: "RepoSense with Travis", link: "ug/withTravis.html"},
diff --git a/docs/ug/cli.md b/docs/ug/cli.md
index e5c3794c4d..db0829968d 100644
--- a/docs/ug/cli.md
+++ b/docs/ug/cli.md
@@ -137,6 +137,20 @@ This flag overrides the `Ignore file size limit` field in the CSV config file.
+### `--init`
+
+**`--init`**: Launches the RepoSense CLI set-up wizard.
+* Example: `--init`
+
+
+
+* The wizard will run through the basic config flags in this order: `--since`, `--until`, `--view`, `--repos`.
+* This flag overrides all other flags and thus should be used on its own.
+* A guide of the steps performed by the wizard is located at [Appendix: Using --init flag](./usingInit.html).
+
+
+
+
### `--last-modified-date`, `-l`
**`--last-modified-date`**: Specifies that the last modified date of each line of code should be added to `authorship.json`.
@@ -177,6 +191,8 @@ This flag overrides the `Ignore file size limit` field in the CSV config file.
+
+
### `--repo`, `--repos`, `-r`
**`--repo REPO_LOCATION`**: Specifies which repositories to analyze.
@@ -190,6 +206,8 @@ This flag overrides the `Ignore file size limit` field in the CSV config file.
Cannot be used with `--config`. This flag takes precedence over `--config`.
+
+
### `--shallow-cloning`, `-S`
@@ -206,6 +224,8 @@ Cannot be used with `--last-modified-date`. This may result in an incorrect last
+
+
### `--since`, `-s`
**`--since START_DATE`**: Specifies the start date for the period to be analyzed.
@@ -220,6 +240,7 @@ Cannot be used with `--last-modified-date`. This may result in an incorrect last
* If `d1` is specified as the start date (`--since d1` or `-s d1`), then the program will search for the earliest commit date of all repositories and use that as the start date.
* If `d1` is specified together with `--period`, then the program will warn that the date range being analyzed may be incorrect.
+
### `--timezone`, `-t`
@@ -232,6 +253,8 @@ Cannot be used with `--last-modified-date`. This may result in an incorrect last
+
+
### `--until`, `-u`
**`--until END_DATE`**: Specifies the end date of the analysis period.
@@ -244,6 +267,7 @@ Cannot be used with `--last-modified-date`. This may result in an incorrect last
Note: If the end date is not specified, the date of generating the report will be taken as the end date.
+
@@ -258,6 +282,8 @@ Cannot be used with any other flags. This flag takes precedence over all other f
+
+
### `--view`, `-v`
**`--view [REPORT_FOLDER]`**: Specifies that the report should be opened in the default browser.
@@ -265,3 +291,4 @@ Cannot be used with any other flags. This flag takes precedence over all other f
Default: `./reposense-report`
* Alias: `-v`
* Example:`--view` or `-v`
+
diff --git a/docs/ug/generatingReports.md b/docs/ug/generatingReports.md
index f4bb88f557..5085bedff1 100644
--- a/docs/ug/generatingReports.md
+++ b/docs/ug/generatingReports.md
@@ -26,6 +26,8 @@ For other types of repositories, external links are disabled.
+
+
## Generating reports locally
1. **Ensure you have the prerequisites**:
@@ -44,6 +46,12 @@ For other types of repositories, external links are disabled.
**To learn how to generate a report using other settings**, head over to the [_**Customizing reports**_](customizingReports.html) section.
+**Alternatively**, you may also use the RepoSense CLI set-up wizard, which will take you through a basic configuration of RepoSense through the following command:
+ * `java -jar RepoSense.jar --init`
+
+A guide to explaining each step of the set-up wizard can also be found at [Appendix: Using --init flag](./usingInit.html), if you would like to follow along to better understand how to use RepoSense in the future.
+
+
## Generating reports remotely
diff --git a/docs/ug/usingInit.md b/docs/ug/usingInit.md
new file mode 100644
index 0000000000..631ec1cc22
--- /dev/null
+++ b/docs/ug/usingInit.md
@@ -0,0 +1,74 @@
+{% set title = "Appendix: Using `--init` flag" %}
+
+ title: "{{ title | safe }}"
+
+
+{% from 'scripts/macros.njk' import embed, step with context %}
+
+{{ title }}
+
+
+
+The `--init` flag makes it easier for new users to get into using RepoSense, by guiding them step-by-step through a wizard with the most essential flags.
+
+
+This guide serves to assist new users in understanding what the `--init` flag is doing, and eventually helps them understand how to use RepoSense without the `init` flag.
+
+If you have yet to do so, download the latest JAR file from our releases. The instructions are given below. You may ignore Step 3, as the `init` flag supersedes it.
+
+{{ embed("**Generating reports → Generating reports locally**", "generatingReports.md#section-generating-reports-locally") }}
+
+### Explanation of each step
+
+#### List of repository locations to be analyzed: `--repos`
+
+Upon using `java -jar RepoSense.java --init`, the wizard will ask for a list of repository locations, separated by spaces.
+
+This list of repository locations is necessary, as it will be the repositories analyzed by RepoSense at the end of the wizard.
+
+RepoSense accepts both remote Git repository HTTPS links, as well as paths to local Git repositories.
+
+Given below is the actual CLI flag being used. These embedded subsections will be given throughout this guide to help you pinpoint and locate it in your future references.
+
+{{ embed("**CLI syntax reference → `--repos`**", "cli.md#section-repos") }}
+
+
+
+#### Start date of the analysis: `--since`
+
+The wizard will ask for the start date of the period of the analysis next.
+
+The start date, by default, will be one month before the current date. It is hence not necessary to include when you are using the CLI to run RepoSense, but it is likely that you will want to analyze a period with a different starting date.
+
+Given below is the actual CLI flag being used.
+
+{{ embed("**CLI syntax reference → `--since`**", "cli.md#section-since") }}
+
+#### Start date of the analysis: `--until`
+
+The wizard will ask for the until date of the period of the analysis next.
+
+The until date, by default, will be the current date. Again, it is not necessary to include when you are using the CLI to run RepoSense.
+
+Given below is the actual CLI flag being used.
+
+{{ embed("**CLI syntax reference → `--until`**", "cli.md#section-until") }}
+
+#### Viewing the report locally: `--view`
+
+The wizard will ask if you want to start a local server to view the report immediately after the report is generated. By indicating `Y`, a server will be started on your computer on port 9000, where the report can be viewed at http://localhost:9000/.
+
+This flag is used without any arguments in this basic wizard.
+
+Given below is the actual CLI flag being used.
+
+{{ embed("**CLI syntax reference → `--view`**", "cli.md#section-view") }}
+
+
+
+### Conclusion
+
+At this stage, the `--init` wizard would have completed, and the generation of the RepoSense report is underway. It may take a few seconds to a few minutes, depending on the number of repositories entered, as well as your network speed.
+
+After the report generates, you may want to proceed to [Using reports](usingReports.html) in order to better understand the interface and how you can use the report generated by RepoSense. You may also want to take a closer look at the other config flags provided by RepoSense at [CLI syntax reference](cli.html) to understand what other features are available.
+
diff --git a/src/main/java/reposense/RepoSense.java b/src/main/java/reposense/RepoSense.java
index e16b42fdbc..a186ecb83a 100644
--- a/src/main/java/reposense/RepoSense.java
+++ b/src/main/java/reposense/RepoSense.java
@@ -7,6 +7,7 @@
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
@@ -23,6 +24,7 @@
import reposense.model.RepoLocation;
import reposense.model.ReportConfiguration;
import reposense.model.ViewCliArguments;
+import reposense.model.WizardCliArguments;
import reposense.parser.ArgsParser;
import reposense.parser.AuthorConfigCsvParser;
import reposense.parser.GroupConfigCsvParser;
@@ -36,6 +38,8 @@
import reposense.system.ReportServer;
import reposense.util.FileUtil;
import reposense.util.TimeUtil;
+import reposense.wizard.BasicWizard;
+import reposense.wizard.WizardRunner;
/**
* The main RepoSense class.
@@ -68,6 +72,11 @@ public static void main(String[] args) {
reportConfig = ((ConfigCliArguments) cliArguments).getReportConfiguration();
} else if (cliArguments instanceof LocationsCliArguments) {
configs = getRepoConfigurations((LocationsCliArguments) cliArguments);
+ } else if (cliArguments instanceof WizardCliArguments) {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ wizardRunner.buildInput(new Scanner(System.in));
+ wizardRunner.run();
+ return;
} else {
throw new AssertionError("CliArguments's subclass type is unhandled.");
}
diff --git a/src/main/java/reposense/model/WizardCliArguments.java b/src/main/java/reposense/model/WizardCliArguments.java
new file mode 100644
index 0000000000..39e2d78e64
--- /dev/null
+++ b/src/main/java/reposense/model/WizardCliArguments.java
@@ -0,0 +1,7 @@
+package reposense.model;
+
+/**
+ * Represents command line arguments user supplied when running the program with mandatory field -init.
+ */
+public class WizardCliArguments extends CliArguments {
+}
diff --git a/src/main/java/reposense/parser/ArgsParser.java b/src/main/java/reposense/parser/ArgsParser.java
index b39f048522..f0a288dc94 100644
--- a/src/main/java/reposense/parser/ArgsParser.java
+++ b/src/main/java/reposense/parser/ArgsParser.java
@@ -31,6 +31,7 @@
import reposense.model.LocationsCliArguments;
import reposense.model.ReportConfiguration;
import reposense.model.ViewCliArguments;
+import reposense.model.WizardCliArguments;
import reposense.system.LogsManager;
import reposense.util.TimeUtil;
@@ -65,6 +66,8 @@ public class ArgsParser {
public static final String[] CLONING_THREADS_FLAG = new String[] {"--cloning-threads"};
public static final String[] ANALYSIS_THREADS_FLAG = new String[] {"--analysis-threads"};
+ public static final String[] CLI_WIZARD_FLAGS = new String[] {"--init"};
+
public static final String[] TEST_MODE_FLAG = new String[] {"--test-mode"};
public static final String[] FRESH_CLONING_FLAG = new String[] {"--fresh-cloning"};
@@ -239,6 +242,11 @@ private static ArgumentParser getArgumentParser() {
.setDefault(DEFAULT_NUM_ANALYSIS_THREADS)
.help(FeatureControl.SUPPRESS);
+ parser.addArgument(CLI_WIZARD_FLAGS)
+ .dest(CLI_WIZARD_FLAGS[0])
+ .action(Arguments.storeTrue())
+ .help("Launches the RepoSense Command line wizard to walk through the basic setup.");
+
// Testing flags
argumentGroup.addArgument(TEST_MODE_FLAG)
.dest(TEST_MODE_FLAG[0])
@@ -283,6 +291,11 @@ public static CliArguments parse(String[] args) throws HelpScreenException, Pars
boolean shouldIncludeLastModifiedDate = results.get(LAST_MODIFIED_DATE_FLAGS[0]);
boolean shouldPerformShallowCloning = results.get(SHALLOW_CLONING_FLAGS[0]);
boolean shouldFindPreviousAuthors = results.get(FIND_PREVIOUS_AUTHORS_FLAGS[0]);
+ boolean isWizardCli = results.get(CLI_WIZARD_FLAGS[0]);
+
+ if (isWizardCli) {
+ return new WizardCliArguments();
+ }
// Report config is ignored if --repos is provided
if (locations == null) {
diff --git a/src/main/java/reposense/wizard/BasicWizard.java b/src/main/java/reposense/wizard/BasicWizard.java
new file mode 100644
index 0000000000..4dfd38032b
--- /dev/null
+++ b/src/main/java/reposense/wizard/BasicWizard.java
@@ -0,0 +1,22 @@
+package reposense.wizard;
+
+import reposense.wizard.prompt.OptionalSincePrompt;
+import reposense.wizard.prompt.OptionalUntilPrompt;
+import reposense.wizard.prompt.Prompt;
+import reposense.wizard.prompt.RepoPrompt;
+import reposense.wizard.prompt.ViewPrompt;
+
+/**
+ * Represents a basic wizard run.
+ */
+public class BasicWizard extends Wizard {
+ private static final Prompt[] INITIAL_PROMPTS = new Prompt[] {
+ new RepoPrompt(),
+ new OptionalSincePrompt(),
+ new OptionalUntilPrompt(),
+ new ViewPrompt()
+ };
+ public BasicWizard() {
+ super(INITIAL_PROMPTS);
+ }
+}
diff --git a/src/main/java/reposense/wizard/InputBuilder.java b/src/main/java/reposense/wizard/InputBuilder.java
new file mode 100644
index 0000000000..daf9aa87e2
--- /dev/null
+++ b/src/main/java/reposense/wizard/InputBuilder.java
@@ -0,0 +1,98 @@
+package reposense.wizard;
+
+import java.nio.file.Path;
+
+import reposense.parser.ArgsParser;
+
+/**
+ * A utility class to help with building command line input.
+ * Example usage:
+ * {@code String input = new InputBuilder().addSinceDate("27/01/2017").build();}
+ */
+public class InputBuilder {
+ private static final String WHITESPACE = " ";
+
+ private StringBuilder input;
+
+ public InputBuilder() {
+ init();
+ }
+
+ /**
+ * Initialize variables to default values.
+ * Used by {@link InputBuilder#InputBuilder() constructor}.
+ */
+ private void init() {
+ input = new StringBuilder();
+ }
+
+ /**
+ * Returns the {@code input} generated from this {@link InputBuilder}.
+ */
+ public String build() {
+ return input.toString();
+ }
+
+ /**
+ * Adds the repo flag with the {@code paths} as arguments to the input.
+ * This method should only be called once in one build.
+ *
+ * @param paths The repo paths.
+ */
+ public InputBuilder addRepos(String paths) {
+ input.append(ArgsParser.REPO_FLAGS[0] + WHITESPACE + paths + WHITESPACE);
+ return this;
+ }
+
+ /**
+ * Adds the view flag with the {@code path} as argument to the input.
+ * This method should only be called once in one build.
+ *
+ * @param path The view folder path.
+ */
+ public InputBuilder addView(Path path) {
+ input.append(ArgsParser.VIEW_FLAGS[0] + WHITESPACE + addQuotationMarksToPath(path) + WHITESPACE);
+ return this;
+ }
+
+ /**
+ * Adds the view flag only to the input.
+ * This method should only be called once in one build.
+ */
+ public InputBuilder addView() {
+ input.append(ArgsParser.VIEW_FLAGS[0] + WHITESPACE);
+ return this;
+ }
+
+ /**
+ * Adds the since flag with the {@code date} as argument to the input.
+ * This method should only be called once in one build.
+ *
+ * @param date The since date.
+ */
+ public InputBuilder addSinceDate(String date) {
+ input.append(ArgsParser.SINCE_FLAGS[0] + WHITESPACE + date + WHITESPACE);
+ return this;
+ }
+
+ /**
+ * Adds the until flag with the {@code date} as argument to the input.
+ * This method should only be called once in one build.
+ *
+ * @param date The until date.
+ */
+ public InputBuilder addUntilDate(String date) {
+ input.append(ArgsParser.UNTIL_FLAGS[0] + WHITESPACE + date + WHITESPACE);
+ return this;
+ }
+
+
+ private static String addQuotationMarksToPath(String path) {
+ return '"' + path + '"';
+ }
+
+ private static String addQuotationMarksToPath(Path path) {
+ return addQuotationMarksToPath(path.toString());
+ }
+
+}
diff --git a/src/main/java/reposense/wizard/Wizard.java b/src/main/java/reposense/wizard/Wizard.java
new file mode 100644
index 0000000000..68fc97d0ea
--- /dev/null
+++ b/src/main/java/reposense/wizard/Wizard.java
@@ -0,0 +1,22 @@
+package reposense.wizard;
+
+import reposense.wizard.prompt.Prompt;
+
+/**
+ * This class implements an abstract wizard.
+ * A concrete implementation of a wizard contains
+ * prompts specific to that wizard.
+ */
+public abstract class Wizard {
+ // contains the basic skeleton of the wizard.
+
+ private final Prompt[] initialPrompts;
+
+ public Wizard(Prompt[] initialPrompts) {
+ this.initialPrompts = initialPrompts;
+ }
+
+ public Prompt[] getInitialPrompts() {
+ return initialPrompts;
+ };
+}
diff --git a/src/main/java/reposense/wizard/WizardRunner.java b/src/main/java/reposense/wizard/WizardRunner.java
new file mode 100644
index 0000000000..afb29a7580
--- /dev/null
+++ b/src/main/java/reposense/wizard/WizardRunner.java
@@ -0,0 +1,55 @@
+package reposense.wizard;
+
+import static org.apache.tools.ant.types.Commandline.translateCommandline;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Scanner;
+
+import reposense.RepoSense;
+import reposense.wizard.prompt.Prompt;
+
+/**
+ * This class implements a wizard runner in the form
+ * of a discrete event simulator.
+ * It maintains a queue of prompts and run through
+ * the prompts until the queue is empty.
+ */
+public class WizardRunner {
+ private final Deque prompts;
+ private InputBuilder inputBuilder;
+
+ public WizardRunner(Wizard wizard) {
+ prompts = new ArrayDeque<>();
+ for (Prompt p : wizard.getInitialPrompts()) {
+ prompts.add(p);
+ }
+ inputBuilder = new InputBuilder();
+ }
+
+ /**
+ * Run the wizard until no more prompts is in the queue.
+ * If the wizard returns one or more prompts, add them
+ * to the queue, and repeat.
+ */
+ public void buildInput(Scanner sc) {
+ Prompt prompt = prompts.poll();
+ while (prompt != null) {
+ prompt.promptAndGetInput(sc);
+ inputBuilder = prompt.addToInput(inputBuilder);
+ Prompt[] newPrompts = prompt.run();
+ for (int i = newPrompts.length - 1; i >= 0; i--) {
+ prompts.addFirst(newPrompts[i]);
+ }
+ prompt = prompts.poll();
+ }
+ }
+
+ public String getBuiltInput() {
+ return inputBuilder.build();
+ }
+
+ public void run() {
+ RepoSense.main(translateCommandline(getBuiltInput()));
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/OptionalPrompt.java b/src/main/java/reposense/wizard/prompt/OptionalPrompt.java
new file mode 100644
index 0000000000..6ddb514b36
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/OptionalPrompt.java
@@ -0,0 +1,38 @@
+package reposense.wizard.prompt;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents an optional prompt that only calls the provided prompt if requested by the user.
+ */
+public abstract class OptionalPrompt extends Prompt {
+ public static final String YES_FLAG = "Y";
+ public static final String NO_FLAG = "N";
+ private static final String FORMAT = String.format("%s/%s", YES_FLAG, NO_FLAG);
+
+ public OptionalPrompt(String description) {
+ super(description, FORMAT);
+ }
+
+ // optionally run array of Prompts if YES_FLAG is provided as input
+ public abstract Prompt[] optionallyRun();
+
+ @Override
+ public InputBuilder addToInput(InputBuilder inputBuilder) {
+ return inputBuilder;
+ }
+
+ @Override
+ public Prompt[] run() {
+ if (getResponse().compareToIgnoreCase(YES_FLAG) == 0) {
+ return optionallyRun();
+ }
+
+ if (getResponse().compareToIgnoreCase(NO_FLAG) == 0) {
+ return new Prompt[] {};
+ }
+
+ // Repeat OptionalPrompt until a valid input is provided
+ return new Prompt[] {this};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/OptionalSincePrompt.java b/src/main/java/reposense/wizard/prompt/OptionalSincePrompt.java
new file mode 100644
index 0000000000..47fcffd382
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/OptionalSincePrompt.java
@@ -0,0 +1,18 @@
+package reposense.wizard.prompt;
+
+/**
+ * Represents an Optional Prompt to get the sinceDate flag.
+ */
+public class OptionalSincePrompt extends OptionalPrompt {
+ private static final String DESCRIPTION = "Do you want to specify the start date for the period to be analyzed? "
+ + "The default is one month before the current date";
+
+ public OptionalSincePrompt() {
+ super(DESCRIPTION);
+ }
+
+ @Override
+ public Prompt[] optionallyRun() {
+ return new Prompt[]{new SincePrompt()};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/OptionalUntilPrompt.java b/src/main/java/reposense/wizard/prompt/OptionalUntilPrompt.java
new file mode 100644
index 0000000000..bcf873e9d3
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/OptionalUntilPrompt.java
@@ -0,0 +1,18 @@
+package reposense.wizard.prompt;
+
+/**
+ * Represents an Optional Prompt to get the untilDate flag.
+ */
+public class OptionalUntilPrompt extends OptionalPrompt {
+ private static final String DESCRIPTION = "Do you want to specify the end date for the period to be analyzed? "
+ + "The default is today";
+
+ public OptionalUntilPrompt() {
+ super(DESCRIPTION);
+ }
+
+ @Override
+ public Prompt[] optionallyRun() {
+ return new Prompt[]{new UntilPrompt()};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/Prompt.java b/src/main/java/reposense/wizard/prompt/Prompt.java
new file mode 100644
index 0000000000..d2290551da
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/Prompt.java
@@ -0,0 +1,58 @@
+package reposense.wizard.prompt;
+
+import java.util.Scanner;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents an abstract prompt.
+ */
+public abstract class Prompt {
+ private final String description;
+ private final String format;
+ private String response;
+
+ public Prompt(String description, String format) {
+ this.description = description;
+ this.format = format;
+ }
+
+ public Prompt(String description) {
+ this.description = description;
+ this.format = "";
+ }
+
+ /**
+ * Prompts the user for an input and stores it in response.
+ *
+ * @param sc Scanner that takes in the input
+ */
+ public void promptAndGetInput(Scanner sc) {
+ System.out.println(this);
+ response = getInput(sc);
+ }
+
+ private String getInput(Scanner sc) {
+ return sc.nextLine();
+ }
+
+ public String getResponse() {
+ return response.trim();
+ }
+
+ public abstract InputBuilder addToInput(InputBuilder inputBuilder);
+
+ // each prompt may have additional effects depending on the prompt.
+ // when we run a prompt, the prompt will ask for input.
+ // for example, repos prompt will launch a vim window.
+ // a prompt may also create other prompts () (for example a open vim prompt)
+ public abstract Prompt[] run();
+
+ @Override
+ public String toString() {
+ if (format.isEmpty()) {
+ return String.format("%s: ", description);
+ }
+ return String.format("%s (%s): ", description, format);
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/RepoPrompt.java b/src/main/java/reposense/wizard/prompt/RepoPrompt.java
new file mode 100644
index 0000000000..be025c967f
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/RepoPrompt.java
@@ -0,0 +1,25 @@
+package reposense.wizard.prompt;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents a Prompt to get the repo flag.
+ */
+public class RepoPrompt extends Prompt {
+ private static final String DESCRIPTION = "Enter a list of URLs or the disk location of the git repositories "
+ + "to analyze, separated by spaces";
+
+ public RepoPrompt() {
+ super(DESCRIPTION);
+ }
+
+ @Override
+ public InputBuilder addToInput(InputBuilder inputBuilder) {
+ return inputBuilder.addRepos(getResponse());
+ }
+
+ @Override
+ public Prompt[] run() {
+ return new Prompt[] {};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/SincePrompt.java b/src/main/java/reposense/wizard/prompt/SincePrompt.java
new file mode 100644
index 0000000000..768f811dd7
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/SincePrompt.java
@@ -0,0 +1,25 @@
+package reposense.wizard.prompt;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents a Prompt to get the sinceDate flag.
+ */
+public class SincePrompt extends Prompt {
+ private static final String DESCRIPTION = "Enter the start date for the period to be analyzed";
+ private static final String FORMAT = "DD/MM/YYYY";
+
+ public SincePrompt() {
+ super(DESCRIPTION, FORMAT);
+ }
+
+ @Override
+ public InputBuilder addToInput(InputBuilder inputBuilder) {
+ return inputBuilder.addSinceDate(getResponse());
+ }
+
+ @Override
+ public Prompt[] run() {
+ return new Prompt[] {};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/UntilPrompt.java b/src/main/java/reposense/wizard/prompt/UntilPrompt.java
new file mode 100644
index 0000000000..7f019e86cb
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/UntilPrompt.java
@@ -0,0 +1,25 @@
+package reposense.wizard.prompt;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents a Prompt to get the untilDate flag.
+ */
+public class UntilPrompt extends Prompt {
+ private static final String DESCRIPTION = "Enter the end date for the period to be analyzed";
+ private static final String FORMAT = "DD/MM/YYYY";
+
+ public UntilPrompt() {
+ super(DESCRIPTION, FORMAT);
+ }
+
+ @Override
+ public InputBuilder addToInput(InputBuilder inputBuilder) {
+ return inputBuilder.addUntilDate(getResponse());
+ }
+
+ @Override
+ public Prompt[] run() {
+ return new Prompt[] {};
+ }
+}
diff --git a/src/main/java/reposense/wizard/prompt/ViewPrompt.java b/src/main/java/reposense/wizard/prompt/ViewPrompt.java
new file mode 100644
index 0000000000..0a6e92b529
--- /dev/null
+++ b/src/main/java/reposense/wizard/prompt/ViewPrompt.java
@@ -0,0 +1,37 @@
+package reposense.wizard.prompt;
+
+import reposense.wizard.InputBuilder;
+
+/**
+ * Represents a Prompt to get the view flag.
+ */
+public class ViewPrompt extends Prompt {
+ private static final String DESCRIPTION = "Do you want to start a server to display the report?";
+ private static final String YES_FLAG = "Y";
+ private static final String NO_FLAG = "N";
+ private static final String FORMAT = String.format("%s/%s", YES_FLAG, NO_FLAG);
+
+ public ViewPrompt() {
+ super(DESCRIPTION, FORMAT);
+ }
+
+ @Override
+ public InputBuilder addToInput(InputBuilder inputBuilder) {
+ if (getResponse().compareToIgnoreCase(YES_FLAG) == 0) {
+ return inputBuilder.addView();
+ }
+
+ return inputBuilder;
+ }
+
+ @Override
+ public Prompt[] run() {
+ if (getResponse().compareToIgnoreCase(YES_FLAG) == 0
+ || getResponse().compareToIgnoreCase(NO_FLAG) == 0) {
+ return new Prompt[] {};
+ }
+
+ // Repeat ViewPrompt until a valid input is provided
+ return new Prompt[] {this};
+ }
+}
diff --git a/src/test/java/reposense/util/WizardInputBuilder.java b/src/test/java/reposense/util/WizardInputBuilder.java
new file mode 100644
index 0000000000..e8251d2745
--- /dev/null
+++ b/src/test/java/reposense/util/WizardInputBuilder.java
@@ -0,0 +1,51 @@
+package reposense.util;
+
+import reposense.wizard.prompt.OptionalPrompt;
+
+/**
+ * A utility class to help with building command line input for Wizard CLI.
+ * Example usage:
+ * {@code String input = new WizardInputBuilder().append("22/02/2022").build();}
+ */
+public class WizardInputBuilder {
+ private static final String NEW_LINE = "\n";
+ private StringBuilder input;
+
+ public WizardInputBuilder() {
+ this.input = new StringBuilder();
+ }
+
+ /**
+ * Adds command entered by the user to the input.
+ *
+ * @param command The command by the user
+ */
+ public WizardInputBuilder add(String command) {
+ input.append(command).append(NEW_LINE);
+ return this;
+ }
+
+ /**
+ * Adds Yes Flag entered by the user to the input.
+ */
+ public WizardInputBuilder addYesFlag() {
+ input.append(OptionalPrompt.YES_FLAG).append(NEW_LINE);
+ return this;
+ }
+
+ /**
+ * Adds No Flag entered by the user to the input.
+ */
+ public WizardInputBuilder addNoFlag() {
+ input.append(OptionalPrompt.NO_FLAG).append(NEW_LINE);
+ return this;
+ }
+
+ /**
+ * Returns the {@code input} generated from this {@link WizardInputBuilder}.
+ */
+ public String build() {
+ return input.toString();
+ }
+
+}
diff --git a/src/test/java/reposense/wizard/WizardRunnerTest.java b/src/test/java/reposense/wizard/WizardRunnerTest.java
new file mode 100644
index 0000000000..801b383661
--- /dev/null
+++ b/src/test/java/reposense/wizard/WizardRunnerTest.java
@@ -0,0 +1,146 @@
+package reposense.wizard;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Scanner;
+
+import org.junit.jupiter.api.Test;
+
+import reposense.util.WizardInputBuilder;
+
+public class WizardRunnerTest {
+ private static final String SINCE_DATE = "30/09/2022";
+ private static final String UNTIL_DATE = "15/03/2023";
+ private static final String REPO_LINK = "https://github.com/reposense/RepoSense.git";
+
+ @Test
+ public void buildInput_repoOnly_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addNoFlag()
+ .addNoFlag()
+ .addNoFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder().addRepos(REPO_LINK).build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+
+ @Test
+ public void buildInput_sinceDateAndRepo_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ // Yes only for sinceDate flag
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addYesFlag()
+ .add(SINCE_DATE)
+ .addNoFlag()
+ .addNoFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder()
+ .addRepos(REPO_LINK)
+ .addSinceDate(SINCE_DATE)
+ .build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+
+ @Test
+ public void buildInput_untilDateAndRepo_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addNoFlag()
+ .addYesFlag()
+ .add(UNTIL_DATE)
+ .addNoFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder()
+ .addRepos(REPO_LINK)
+ .addUntilDate(UNTIL_DATE)
+ .build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+
+ @Test
+ public void buildInput_sinceDateAndUntilDateAndRepo_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addYesFlag()
+ .add(SINCE_DATE)
+ .addYesFlag()
+ .add(UNTIL_DATE)
+ .addNoFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder()
+ .addRepos(REPO_LINK)
+ .addSinceDate(SINCE_DATE)
+ .addUntilDate(UNTIL_DATE)
+ .build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+
+ @Test
+ public void buildInput_viewAndRepo_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ // Yes only for view flag
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addNoFlag()
+ .addNoFlag()
+ .addYesFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder()
+ .addRepos(REPO_LINK)
+ .addView()
+ .build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+
+ @Test
+ public void buildInput_allFlags_success() {
+ WizardRunner wizardRunner = new WizardRunner(new BasicWizard());
+ // Yes for all flags
+ String input = new WizardInputBuilder()
+ .add(REPO_LINK)
+ .addYesFlag()
+ .add(SINCE_DATE)
+ .addYesFlag()
+ .add(UNTIL_DATE)
+ .addYesFlag()
+ .build();
+ InputStream targetStream = new ByteArrayInputStream(input.getBytes());
+ Scanner sc = new Scanner(targetStream);
+ wizardRunner.buildInput(sc);
+
+ String expectedInput = new InputBuilder()
+ .addRepos(REPO_LINK)
+ .addSinceDate(SINCE_DATE)
+ .addUntilDate(UNTIL_DATE)
+ .addView()
+ .build();
+ assertEquals(expectedInput, wizardRunner.getBuiltInput());
+ }
+}