diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..5d45982c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.vs/
+bin/
+obj/
+
+TestResults/
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj b/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj
new file mode 100644
index 00000000..3c6aee7d
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/CashDrawer.App.Tests.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net5.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt
new file mode 100644
index 00000000..2ff39cc0
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderData.txt
@@ -0,0 +1,3 @@
+1.00,2.00
+3.00,4.00
+5.00,6.00
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs
new file mode 100644
index 00000000..38a4dd33
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/InputFileReaderTests.cs
@@ -0,0 +1,39 @@
+using CashDrawer.App.FileReaders;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CashDrawer.App.Tests.FileReaderTests
+{
+ [TestClass]
+ public class InputFileReaderTests
+ {
+
+ [TestMethod]
+ public void file_reader_reads_input_from_a_file()
+ {
+ var filename = @"FileReaderTests\InputFileReaderData.txt";
+
+ var reader = new InputFileReader(filename, new LineParser());
+
+ Assert.IsTrue(reader.HaveMore);
+ var line = reader.Next();
+ Assert.IsFalse(line.HasError);
+ Assert.AreEqual(1.00m, line.Due);
+ Assert.AreEqual(2.00m, line.Paid);
+
+ Assert.IsTrue(reader.HaveMore);
+ line = reader.Next();
+ Assert.IsFalse(line.HasError);
+ Assert.AreEqual(3.00m, line.Due);
+ Assert.AreEqual(4.00m, line.Paid);
+
+ Assert.IsTrue(reader.HaveMore);
+ line = reader.Next();
+ Assert.IsFalse(line.HasError);
+ Assert.AreEqual(5.00m, line.Due);
+ Assert.AreEqual(6.00m, line.Paid);
+
+ Assert.IsFalse(reader.HaveMore);
+ }
+
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs
new file mode 100644
index 00000000..874879bf
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileReaderTests/LineParserTests.cs
@@ -0,0 +1,116 @@
+using CashDrawer.App.FileReaders;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CashDrawer.App.Tests.FileReaderTests
+{
+
+ [TestClass]
+ public class LineParserTests
+ {
+
+ [TestMethod]
+ public void parser_can_parse_line_with_good_due_and_paid_values()
+ {
+ var line = "10.00,12.00";
+
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsFalse(result.HasError);
+ Assert.AreEqual(10.00m, result.Due);
+ Assert.AreEqual(12.00m, result.Paid);
+ }
+
+
+
+ [TestMethod]
+ public void parser_ignores_white_space()
+ {
+ var line = " 10.00 , 12.00 ";
+
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsFalse(result.HasError);
+ Assert.AreEqual(10.00m, result.Due);
+ Assert.AreEqual(12.00m, result.Paid);
+ }
+
+
+
+ [TestMethod]
+ [DataRow("1.00")]
+ [DataRow("1.00,")]
+ [DataRow("1.00, 2.00, 3.00")]
+ [DataRow("")]
+ [DataRow(",")]
+ public void parser_returns_error_if_not_two_amounts_on_line(string line)
+ {
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsTrue(result.HasError);
+ Assert.AreEqual("Invalid line. Expected format ,", result.Error);
+ }
+
+
+
+ [TestMethod]
+ public void parser_returns_error_if_invalid_due_amount()
+ {
+ var line = "X.XX, 1.00";
+
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsTrue(result.HasError);
+ Assert.AreEqual("Invalid amount 'X.XX'", result.Error);
+ }
+
+
+
+ [TestMethod]
+ public void parser_returns_error_if_invalid_paid_amount()
+ {
+ var line = "1.00, X.XX";
+
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsTrue(result.HasError);
+ Assert.AreEqual("Invalid amount 'X.XX'", result.Error);
+ }
+
+
+
+ [TestMethod]
+ [DataRow("1 , 0.00", "Invalid amount '1'. Amount must have 2 digits after the decimal.")]
+ [DataRow("1. , 0.00", "Invalid amount '1.'. Amount must have 2 digits after the decimal.")]
+ [DataRow("1.1 , 0.00", "Invalid amount '1.1'. Amount must have 2 digits after the decimal.")]
+ public void parser_returns_error_if_due_amount_does_not_have_exactly_two_digits_after_decimal(string line, string expectedError)
+ {
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsTrue(result.HasError);
+ Assert.AreEqual(expectedError, result.Error);
+ }
+
+
+
+ [TestMethod]
+ [DataRow("0.00, 1 ", "Invalid amount '1'. Amount must have 2 digits after the decimal.")]
+ [DataRow("0.00, 1. ", "Invalid amount '1.'. Amount must have 2 digits after the decimal.")]
+ [DataRow("0.00, 1.1 ", "Invalid amount '1.1'. Amount must have 2 digits after the decimal.")]
+ public void parser_returns_error_if_paid_amount_does_not_have_exactly_two_digits_after_decimal(string line, string expectedError)
+ {
+ var parser = new LineParser();
+ var result = parser.Parse(line);
+
+ Assert.IsTrue(result.HasError);
+ Assert.AreEqual(expectedError, result.Error);
+ }
+
+ }
+
+}
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs
new file mode 100644
index 00000000..d6b5aa2f
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/HumanizerTests.cs
@@ -0,0 +1,84 @@
+using CashDrawer.App.FileWriters;
+using CashDrawer.Core;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CashDrawer.App.Tests.FileWriterTests
+{
+ [TestClass]
+ public class HumanizerTests
+ {
+
+ [TestMethod]
+ public void humanizer_converts_change_to_friendly_string()
+ {
+
+ var change = new Change(5, 4, 3, 2, 1);
+ var humanizer = new Humanizer();
+ var text = humanizer.Humanize(change);
+
+ Assert.AreEqual("5 dollars, 4 quarters, 3 dimes, 2 nickles, 1 penny", text);
+ }
+
+
+
+ [TestMethod]
+ public void humanizer_converts_uses_singular_nouns_when_change_values_are_1()
+ {
+
+ var change = new Change(1, 1, 1, 1, 1);
+ var humanizer = new Humanizer();
+ var text = humanizer.Humanize(change);
+
+ Assert.AreEqual("1 dollar, 1 quarter, 1 dime, 1 nickle, 1 penny", text);
+ }
+
+
+
+ [TestMethod]
+ public void humanizer_converts_uses_plural_nouns_when_change_values_are_greater_than_0()
+ {
+
+ var change = new Change(2, 2, 2, 2, 2);
+ var humanizer = new Humanizer();
+ var text = humanizer.Humanize(change);
+
+ Assert.AreEqual("2 dollars, 2 quarters, 2 dimes, 2 nickles, 2 pennies", text);
+ }
+
+
+
+ [TestMethod]
+ [DataRow(0, 1, 1, 1, 1, "1 quarter, 1 dime, 1 nickle, 1 penny")]
+ [DataRow(1, 0, 1, 1, 1, "1 dollar, 1 dime, 1 nickle, 1 penny")]
+ [DataRow(1, 1, 0, 1, 1, "1 dollar, 1 quarter, 1 nickle, 1 penny")]
+ [DataRow(1, 1, 1, 0, 1, "1 dollar, 1 quarter, 1 dime, 1 penny")]
+ [DataRow(1, 1, 1, 1, 0, "1 dollar, 1 quarter, 1 dime, 1 nickle")]
+ public void humanizer_does_not_display_change_amount_if_value_is_zero(int dollars,
+ int quarters,
+ int dimes,
+ int nickles,
+ int pennies,
+ string expectedText)
+ {
+ var change = new Change(dollars, quarters, dimes, nickles, pennies);
+ var humanizer = new Humanizer();
+ var text = humanizer.Humanize(change);
+
+ Assert.AreEqual(expectedText, text);
+ }
+
+
+
+ [TestMethod]
+ public void humanizer_returns_message_if_no_change_due()
+ {
+
+ var change = new Change(0, 0, 0, 0, 0);
+ var humanizer = new Humanizer();
+ var text = humanizer.Humanize(change);
+
+ Assert.AreEqual("no change due", text);
+ }
+
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs
new file mode 100644
index 00000000..05221ed3
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/FileWriterTests/OutputFileWriterTests.cs
@@ -0,0 +1,34 @@
+using CashDrawer.App.FileWriters;
+using CashDrawer.Core;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.IO;
+
+namespace CashDrawer.App.Tests.FileWriterTests
+{
+ [TestClass]
+ public class OutputFileWriterTests
+ {
+
+ [TestMethod]
+ public void file_writer_writes_change_to_a_file()
+ {
+ var filename = "FileWriterTest.txt";
+
+ File.Delete(filename);
+
+ var writer = new OutputFileWriter(filename, new Humanizer());
+
+ var change1 = new Change(0, 1, 2, 3, 4);
+ var change2 = new Change(5, 6, 7, 8, 9);
+ writer.Write(change1);
+ writer.Write(change2);
+
+ var output = File.ReadAllLines(filename);
+
+ Assert.AreEqual(2, output.Length);
+ Assert.AreEqual("1 quarter, 2 dimes, 3 nickles, 4 pennies", output[0]);
+ Assert.AreEqual("5 dollars, 6 quarters, 7 dimes, 8 nickles, 9 pennies", output[1]);
+ }
+
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs b/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs
new file mode 100644
index 00000000..0bf94577
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/Helpers/DummyConsole.cs
@@ -0,0 +1,28 @@
+using System;
+using System.IO;
+
+namespace CashDrawer.App.Tests.Helpers
+{
+ public sealed class DummyConsole : IDisposable
+ {
+ private readonly TextWriter _oldWriter;
+ private readonly StringWriter _newWriter;
+
+ public string Text => _newWriter.ToString();
+
+
+ public DummyConsole()
+ {
+ _newWriter = new StringWriter();
+ _oldWriter = Console.Out;
+ Console.SetOut(_newWriter);
+ }
+
+
+ public void Dispose()
+ {
+ Console.SetOut(_oldWriter);
+ _newWriter.Dispose();
+ }
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Empty.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt
new file mode 100644
index 00000000..b812326d
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/InputFile-Good.txt
@@ -0,0 +1,2 @@
+2.12,3.00
+1.97,2.00
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs
new file mode 100644
index 00000000..d28a1c9d
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App.Tests/MainTests/RunnerTests.cs
@@ -0,0 +1,72 @@
+using CashDrawer.App.Tests.Helpers;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.IO;
+
+namespace CashDrawer.App.Tests.MainTests
+{
+ [TestClass]
+ public class RunnerTests
+ {
+
+ [TestMethod]
+ [DataRow(0)]
+ [DataRow(1)]
+ [DataRow(3)]
+ public void runner_writes_error_to_console_if_arg_count_incorrect(int argCount)
+ {
+ using var console = new DummyConsole();
+
+ var args = new string[argCount];
+ var runner = new Runner();
+ runner.Run(args);
+
+ Assert.IsTrue(console.Text.StartsWith("Invalid command."), console.Text);
+ }
+
+
+
+ [TestMethod]
+ public void runner_writes_error_to_console_if_input_file_not_found()
+ {
+ using var console = new DummyConsole();
+
+ var args = new [] { "missing-file.txt", @"MainTests\out.txt" };
+ var runner = new Runner();
+ runner.Run(args);
+
+ Assert.AreEqual("Input file not found." + Environment.NewLine, console.Text);
+ }
+
+
+
+ [TestMethod]
+ public void runner_writes_error_to_console_if_input_file_is_empty()
+ {
+ using var console = new DummyConsole();
+
+ var args = new [] { @"MainTests\InputFile-Empty.txt", @"MainTests\out.txt" };
+ var runner = new Runner();
+ runner.Run(args);
+
+ Assert.AreEqual("Input file is empty. Nothing to process." + Environment.NewLine, console.Text);
+ }
+
+
+
+ [TestMethod]
+ public void runner_writes_results_to_output_file()
+ {
+ var outputFileName = @"MainTests\out.txt";
+ var args = new[] { @"MainTests\InputFile-Good.txt", outputFileName };
+ var runner = new Runner();
+ runner.Run(args);
+
+ var output = File.ReadAllLines(outputFileName);
+
+ Assert.AreEqual(2, output.Length);
+ Assert.AreEqual("3 quarters, 1 dime, 3 pennies", output[0]);
+ Assert.AreEqual("3 pennies", output[1]);
+ }
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj b/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj
new file mode 100644
index 00000000..67c3d651
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/CashDrawer.App.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net5.0
+ CashDrawer
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs
new file mode 100644
index 00000000..336063f9
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/ILineParser.cs
@@ -0,0 +1,9 @@
+using CashDrawer.Core.Readers;
+
+namespace CashDrawer.App.FileReaders
+{
+ public interface ILineParser
+ {
+ ReadResult Parse(string line);
+ }
+}
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs
new file mode 100644
index 00000000..d3955635
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/InputFileReader.cs
@@ -0,0 +1,31 @@
+using CashDrawer.Core.Readers;
+using System;
+using System.IO;
+
+namespace CashDrawer.App.FileReaders
+{
+ public sealed class InputFileReader : IDisposable, IInputReader
+ {
+ private StreamReader _reader;
+ private readonly ILineParser _lineParser;
+
+ public InputFileReader(string filename, ILineParser lineParser)
+ {
+ _reader = new StreamReader(filename);
+ _lineParser = lineParser;
+ }
+
+ public void Dispose()
+ {
+ _reader.Dispose();
+ }
+
+ public bool HaveMore => !_reader.EndOfStream;
+
+ public ReadResult Next()
+ {
+ var s = _reader.ReadLine();
+ return _lineParser.Parse(s);
+ }
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs b/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs
new file mode 100644
index 00000000..09ad6e25
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileReaders/LineParser.cs
@@ -0,0 +1,61 @@
+using CashDrawer.Core.Readers;
+using System;
+
+namespace CashDrawer.App.FileReaders
+{
+ public class LineParser : ILineParser
+ {
+ public ReadResult Parse(string line)
+ {
+ var parts = line.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+ if (parts.Length != 2)
+ {
+ return ReadResult.Failed("Invalid line. Expected format ,");
+ }
+
+ var dueResult = ParseAmount(parts[0]);
+ if(dueResult.Error != null)
+ {
+ return ReadResult.Failed(dueResult.Error);
+ }
+
+ var paidResult = ParseAmount(parts[1]);
+ if (paidResult.Error != null)
+ {
+ return ReadResult.Failed(paidResult.Error);
+ }
+
+ return ReadResult.Ok(dueResult.Amount, paidResult.Amount);
+ }
+
+
+
+ private (decimal Amount, string Error) ParseAmount(string s)
+ {
+ if (decimal.TryParse(s, out var amount) == false)
+ {
+ return (0, $"Invalid amount '{s}'");
+ }
+
+ if (DigitsAfterDecimal(s) != 2)
+ {
+ return (0, $"Invalid amount '{s}'. Amount must have 2 digits after the decimal.");
+ }
+
+ return (amount, null);
+ }
+
+
+
+ private int DigitsAfterDecimal(string paidString)
+ {
+ var i = paidString.IndexOf('.');
+ if(i == -1)
+ {
+ return 0;
+ }
+ return paidString.Length - i - 1;
+ }
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs
new file mode 100644
index 00000000..8c4c4f36
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/Humanizer.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using CashDrawer.Core;
+
+namespace CashDrawer.App.FileWriters
+{
+ public class Humanizer : IHumanizer
+ {
+
+ public string Humanize(Change change)
+ {
+ var s = new StringBuilder();
+ AddPart(s, change.Dollars, "dollar", "dollars");
+ AddPart(s, change.Quarters, "quarter", "quarters");
+ AddPart(s, change.Dimes, "dime", "dimes");
+ AddPart(s, change.Nickles, "nickle", "nickles");
+ AddPart(s, change.Pennies, "penny", "pennies");
+
+ if (s.Length == 0) return "no change due";
+ return string.Join(", ", s);
+ }
+
+
+ private void AddPart(StringBuilder s, int count, string singular, string plural)
+ {
+ if (count == 0) return;
+ var text = GetText(count, singular, plural);
+ if (s.Length > 0) s.Append(", ");
+ s.Append(text);
+ }
+
+
+ private string GetText(int count, string singular, string plural)
+ {
+ return count == 1 ?
+ $"{count} {singular}" :
+ $"{count} {plural}";
+ }
+
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs
new file mode 100644
index 00000000..48024c1b
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/IHumanizer.cs
@@ -0,0 +1,9 @@
+using CashDrawer.Core;
+
+namespace CashDrawer.App.FileWriters
+{
+ public interface IHumanizer
+ {
+ string Humanize(Change change);
+ }
+}
\ No newline at end of file
diff --git a/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs b/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs
new file mode 100644
index 00000000..393cf168
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/FileWriters/OutputFileWriter.cs
@@ -0,0 +1,33 @@
+using CashDrawer.Core;
+using CashDrawer.Core.Writers;
+using System;
+using System.IO;
+
+namespace CashDrawer.App.FileWriters
+{
+ public class OutputFileWriter : IOutputWriter
+ {
+ private readonly string _filename;
+ private readonly IHumanizer _humanizer;
+
+
+ public OutputFileWriter(string filename, IHumanizer humanizer)
+ {
+ _filename = filename;
+ _humanizer = humanizer;
+ }
+
+
+ public void Write(Change change)
+ {
+ var line = _humanizer.Humanize(change);
+ File.AppendAllText(_filename, line + Environment.NewLine);
+ }
+
+
+ public void WriteError(string error)
+ {
+ File.AppendAllText(_filename, error);
+ }
+ }
+}
diff --git a/CreativeCashDrawer/CashDrawer.App/Program.cs b/CreativeCashDrawer/CashDrawer.App/Program.cs
new file mode 100644
index 00000000..5f8a13d7
--- /dev/null
+++ b/CreativeCashDrawer/CashDrawer.App/Program.cs
@@ -0,0 +1,61 @@
+using CashDrawer.App.FileReaders;
+using CashDrawer.App.FileWriters;
+using CashDrawer.Core;
+using CashDrawer.Core.ChangeCalculatorFactories;
+using System;
+using System.IO;
+
+namespace CashDrawer.App
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var runner = new Runner();
+ runner.Run(args);
+ }
+ }
+
+
+ public class Runner
+ {
+ public void Run(string[] args)
+ {
+ if (args.Length != 2)
+ {
+ Console.WriteLine("Invalid command.");
+ Console.WriteLine("Usage.........: CashDrawer