diff --git a/Calculator.Console/AskUser.cs b/Calculator.Console/AskUser.cs new file mode 100644 index 00000000..8b46eb7b --- /dev/null +++ b/Calculator.Console/AskUser.cs @@ -0,0 +1,29 @@ +namespace Calculator.Console; +using Console = System.Console; + +public static class AskUser +{ + public static T Ask(string[] optionTexts) where T : IParsable + { + bool isValidOption = true; + T? result; + do + { + if (!isValidOption) + { + Console.WriteLine("Choose a correct option"); + } + + foreach (var optionText in optionTexts) + { + Console.WriteLine(optionText); + } + + isValidOption = T.TryParse(Console.ReadLine(), null, out result); + } while (!isValidOption); + + // isValidOption is true when result get correctly parsed so != null + return result!; + } + +} \ No newline at end of file diff --git a/Calculator.Console/Calculator.Console.csproj b/Calculator.Console/Calculator.Console.csproj new file mode 100644 index 00000000..9a478729 --- /dev/null +++ b/Calculator.Console/Calculator.Console.csproj @@ -0,0 +1,14 @@ + + + + + + + + Exe + net10.0 + enable + enable + + + diff --git a/Calculator.Console/Logger.cs b/Calculator.Console/Logger.cs new file mode 100644 index 00000000..65da7b06 --- /dev/null +++ b/Calculator.Console/Logger.cs @@ -0,0 +1,64 @@ +namespace Calculator.Console; +using System.Diagnostics; + +public class Logger : IDisposable +{ + private StreamWriter? _logFile; + private TextWriterTraceListener? _listener; + private readonly string _filePath = "Calculator."; + + public Logger(string fileName) + { + _filePath += fileName; + InitLogger(); + } + + private void InitLogger() + { + _logFile = File.CreateText(_filePath); + _listener = new TextWriterTraceListener(_logFile); + Trace.Listeners.Add(_listener); + Trace.AutoFlush = true; + Intro(_filePath); + } + + public void Dispose() + { + Trace.WriteLine($"Closing log at {DateTime.Now}"); + if (_listener != null) + { + Trace.Listeners.Remove(_listener); + _listener.Dispose(); + _listener = null; + } + if (_logFile != null) + { + _logFile.Dispose(); + _logFile = null; + } + } + + public void Result(double n1, double n2, Library.Operator op, double result) + { + if (_logFile == null) + { + InitLogger(); + } + Trace.WriteLine($"{(op.AmountInput() > 1 ? n1 + " " : "")}{op.GetSymbol()} {n2} = {result}"); + } + + public void DeleteLogs() + { + Dispose(); + if (File.Exists(_filePath)) + { + File.Delete(_filePath); + } + } + + private void Intro(string fileName) + { + Trace.WriteLine($"Starting {fileName} Log"); + Trace.WriteLine(String.Format($"Started {DateTime.Now}")); + } +} \ No newline at end of file diff --git a/Calculator.Console/Program.cs b/Calculator.Console/Program.cs new file mode 100644 index 00000000..35fb26b6 --- /dev/null +++ b/Calculator.Console/Program.cs @@ -0,0 +1,9 @@ +namespace Calculator.Console; + +class Program +{ + static void Main(string[] args) + { + UserInterface.Start(); + } +} \ No newline at end of file diff --git a/Calculator.Console/UserInterface.cs b/Calculator.Console/UserInterface.cs new file mode 100644 index 00000000..1ec75811 --- /dev/null +++ b/Calculator.Console/UserInterface.cs @@ -0,0 +1,91 @@ +using Calculator.Library; + +namespace Calculator.Console; +using Console = System.Console; + +public static class UserInterface +{ + private static double _num1; + private static double _num2; + + // Could add timestamps in the name for persistent logs + // Here we would rather override the file each time + private const string LogName = "calculator.log"; + private static readonly Logger Logs = new Logger(LogName); + private static readonly JsonLogger JsonLogs = new JsonLogger(); + + public static void Start() + { + bool endApp = false; + Intro(); + + while (!endApp) + { + _num1 = AskUserANumber(); + Library.Operator op = AskUserOperand(); + // For some operation, only one number is needed, take the value of num1 for easier print + _num2 = (op.AmountInput() > 1) ? AskUserANumber("second") : _num1; + + try + { + PrintResult(op); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + endApp = AskCloseApp(); + } + + Logs.Dispose(); + JsonLogs.Dispose(); + } + + private static void Intro() + { + // Display title as the C# console calculator app. + Console.WriteLine("Console Calculator in C#\r"); + Console.WriteLine("------------------------\n"); + } + + private static double AskUserANumber(string amount = "first") + { + return AskUser.Ask( + [$"Type the {amount} number, and press ENTER"]); + } + + private static Library.Operator AskUserOperand() + { + return AskUser.Ask( + ["Choose an option from the following list", + "\t+ - Add", + "\t- - Subtract", + "\t* - Multiply", + "\t/ - Divide", + "\tsqrt - Square root", + "\t^ - Power", + "\tcos - Cosinus", + "\tsin - Sinus"]); + } + + private static void PrintResult(Library.Operator op) + { + double result = op.Result(_num1, _num2); + Console.WriteLine($"Your result: {(op.AmountInput() > 1 ? _num1 : "")} {op.GetSymbol()} {_num2} = {result}"); + Logs.Result(_num1, _num2, op, result); + JsonLogs.LogResult(_num1, _num2, op, result); + } + + private static bool AskCloseApp() + { + Console.WriteLine("Press 'n' and Enter to close the app, or press 'd' and Enter to delete the logs or press any other key and Enter to continue: "); + string? input = Console.ReadLine(); + if (input == "d") + { + Logs.DeleteLogs(); + JsonLogs.DeleteLogs(); + return AskCloseApp(); + } + return (input == "n"); + } +} \ No newline at end of file diff --git a/Calculator.Library/Calculator.Library.csproj b/Calculator.Library/Calculator.Library.csproj new file mode 100644 index 00000000..ec19b2b7 --- /dev/null +++ b/Calculator.Library/Calculator.Library.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/Calculator.Library/JsonLogger.cs b/Calculator.Library/JsonLogger.cs new file mode 100644 index 00000000..9d6b44e0 --- /dev/null +++ b/Calculator.Library/JsonLogger.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; + +namespace Calculator.Library; + +public class JsonLogger : IDisposable +{ + private JsonWriter? _writer; + private const string FilePath = "CalculatorLog.json"; + + public JsonLogger() + { + InitLogger(); + } + + private void InitLogger() + { + var logFile = File.CreateText(FilePath); + logFile.AutoFlush = true; + _writer = new JsonTextWriter(logFile); + _writer.Formatting = Formatting.Indented; + _writer.WriteStartObject(); + _writer.WritePropertyName("Operations"); + _writer.WriteStartArray(); + } + + public void LogResult(double n1, double n2, Operator op, double result) + { + if (_writer == null) + { + InitLogger(); + } + _writer!.WriteStartObject(); + _writer.WritePropertyName("Operand1"); + _writer.WriteValue(n1); + if (op.AmountInput() > 1) + { + _writer.WritePropertyName("Operand2"); + _writer.WriteValue(n2); + } + _writer.WritePropertyName("Operation"); + _writer.WriteValue(op.GetSymbol()); + _writer.WritePropertyName("Result"); + _writer.WriteValue(result); + _writer.WriteEndObject(); + } + + public void DeleteLogs() + { + Dispose(); + if (File.Exists(FilePath)) + { + File.Delete(FilePath); + } + } + + public void Dispose() + { + if (_writer == null) return; + + _writer.WriteEndArray(); + _writer.WriteEndObject(); + _writer.Close(); + ((IDisposable)_writer).Dispose(); + _writer = null; + } +} \ No newline at end of file diff --git a/Calculator.Library/Operand.cs b/Calculator.Library/Operand.cs new file mode 100644 index 00000000..f3d22bc4 --- /dev/null +++ b/Calculator.Library/Operand.cs @@ -0,0 +1,96 @@ +namespace Calculator.Library; + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + + +public enum Operand +{ + Addition, + Subtraction, + Multiplication, + Division, + SquareRoot, + Power, + Cos, + Sin +} + +public class Operator : IParsable +{ + private readonly Operand? _operand; + + public Operator(string? input) + { + _operand = input?.ToUpper().Trim() switch + { + "+" => Operand.Addition, + "-" => Operand.Subtraction, + "*" => Operand.Multiplication, + "/" => Operand.Division, + "SQRT" => Operand.SquareRoot, + "^" => Operand.Power, + "COS" => Operand.Cos, + "SIN" => Operand.Sin, + _ => null + }; + } + + public double Result(double a, double b) => _operand switch + { + Operand.Addition => a + b, + Operand.Subtraction => a! - b, + Operand.Multiplication => a * b, + Operand.Division => b != 0 ? a / b : throw new DivideByZeroException("Can't divide by zero"), + Operand.SquareRoot => b >= 0 ? Math.Sqrt(b) : throw new Exception("Square root of negative number isn't a real number"), + Operand.Power => (b % 0 ==0 || a >= 0) ? Math.Pow(a, b) : throw new Exception("Power of negative number with fractional exponent isn't a real number"), + Operand.Cos => Math.Cos(b), + Operand.Sin => Math.Sin(b), + _ => throw new InvalidEnumArgumentException("Operand not recognized") + }; + + public string GetSymbol() => _operand switch + { + Operand.Addition => "+", + Operand.Subtraction => "-", + Operand.Multiplication => "*", + Operand.Division => "/", + Operand.SquareRoot => "SQRT", + Operand.Power => "^", + Operand.Cos => "cos", + Operand.Sin => "sin", + _ => throw new InvalidEnumArgumentException("Operand not recognized") + }; + + public int AmountInput() + { + switch (_operand) + { + case Operand.SquareRoot: + case Operand.Cos: + case Operand.Sin: + return 1; + default: + return 2; + } + } + + public static Operator Parse(string s, IFormatProvider? provider) + { + return TryParse(s, provider, out var result) ? result : throw new FormatException("Not a valid input Operator"); + } + + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Operator result) + { + var op = new Operator(s); + + if (op._operand == null) + { + result = null; + return false; + } + + result = op; + return true; + } +} diff --git a/Calculator.slnx b/Calculator.slnx new file mode 100644 index 00000000..4dee6fc6 --- /dev/null +++ b/Calculator.slnx @@ -0,0 +1,4 @@ + + + +