From 4edfad18b1828ac30ca01636011c31bf59f9a656 Mon Sep 17 00:00:00 2001
From: csc530 <77406318+csc530@users.noreply.github.com>
Date: Sat, 16 Dec 2023 23:54:39 -0500
Subject: [PATCH] :art: style(add job command): use the new text prompt
constructor
simplify the construction of a text prompt to autofill from cmd options
Signed-off-by: csc530 <77406318+csc530@users.noreply.github.com>
---
resume builder/Helpers.cs | 352 ++++++++++--------
.../cli/commands/add/AddJobCommand.cs | 95 ++---
2 files changed, 233 insertions(+), 214 deletions(-)
diff --git a/resume builder/Helpers.cs b/resume builder/Helpers.cs
index 0bea64b..bfb9fcb 100644
--- a/resume builder/Helpers.cs
+++ b/resume builder/Helpers.cs
@@ -1,6 +1,4 @@
-using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Common;
-using System.Reflection;
using Microsoft.Data.Sqlite;
using resume_builder.cli.settings;
using resume_builder.models;
@@ -10,168 +8,206 @@ namespace resume_builder;
public static class Globals
{
- public const string NullString = "";
- public static DateOnly Today { get; } = DateOnly.FromDateTime(DateTime.Today);
-
- public static int PrintError(ExitCode exitCode, string message)
- {
- AnsiConsole.Foreground = Color.Red;
- AnsiConsole.WriteLine($"Error {exitCode.ToInt()}: {exitCode}");
- AnsiConsole.WriteLine(message);
- return exitCode.ToInt();
- }
-
- public static int PrintError(CLISettings settings, Exception exception)
- {
- AnsiConsole.Foreground = Color.Red;
- if(exception.GetType() == typeof(SqliteException))
- {
- var e = (SqliteException)exception;
- var err = $"Database Error {e.SqliteErrorCode}";
- if(settings.Verbose)
- err += $"-{e.SqliteExtendedErrorCode}";
- err += $": {((SqlResultCode)e.SqliteErrorCode).GetMessage()}";
- AnsiConsole.WriteLine($"{err}");
- if(settings.Verbose)
- AnsiConsole.WriteLine($"{e.Message}");
- return ExitCode.DbError.ToInt();
- }
- else if(exception is InvalidOperationException)
- {
- AnsiConsole.WriteLine("Invalid operation");
- return ExitCode.Fail.ToInt();
- }
- else
- {
- AnsiConsole.WriteLine(exception.Message);
- return ExitCode.Error.ToInt();
- }
- }
+ public const string NullString = "";
+ public static DateOnly Today { get; } = DateOnly.FromDateTime(DateTime.Today);
+
+ public static int PrintError(ExitCode exitCode, string message)
+ {
+ AnsiConsole.Foreground = Color.Red;
+ AnsiConsole.WriteLine($"Error {exitCode.ToInt()}: {exitCode}");
+ AnsiConsole.WriteLine(message);
+ return exitCode.ToInt();
+ }
+
+ public static int PrintError(CLISettings settings, Exception exception)
+ {
+ AnsiConsole.Foreground = Color.Red;
+ if(exception.GetType() == typeof(SqliteException))
+ {
+ var e = (SqliteException)exception;
+ var err = $"Database Error {e.SqliteErrorCode}";
+ if(settings.Verbose)
+ err += $"-{e.SqliteExtendedErrorCode}";
+ err += $": {((SqlResultCode)e.SqliteErrorCode).GetMessage()}";
+ AnsiConsole.WriteLine($"{err}");
+ if(settings.Verbose)
+ AnsiConsole.WriteLine($"{e.Message}");
+ return ExitCode.DbError.ToInt();
+ }
+ else if(exception is InvalidOperationException)
+ {
+ AnsiConsole.WriteLine("Invalid operation");
+ return ExitCode.Fail.ToInt();
+ }
+ else
+ {
+ AnsiConsole.WriteLine(exception.Message);
+ return ExitCode.Error.ToInt();
+ }
+ }
}
-public static partial class Extensions
+public static class Extensions
{
- #region conversions
+ // todo: inquire about default value being a property - spectre console pr/iss
+ // .DefaultValue(textPrompt);
- public static int ToInt(this ExitCode exitCode) => (int)exitCode;
- public static DateOnly ToDateOnly(this DateTime date) => DateOnly.FromDateTime(date);
-
- #endregion
-
- // todo: inquire about default value being a property - spectre console pr/iss
- // .DefaultValue(textPrompt);
-
-
- public static string GetPrintValue(this string? value, bool allowBlank = false)
- {
- if(allowBlank)
- return value ?? "";
- return string.IsNullOrWhiteSpace(value) ? Globals.NullString : value;
- }
-
- public static string GetPrintValue(this object? value, bool allowBlank = false) =>
- GetPrintValue(value?.ToString(), allowBlank);
-
- #region sql values
-
- public static object? GetNullableValue(this DbDataReader reader, string columnName)
- {
- var ordinal = reader.GetOrdinal(columnName);
- return reader.GetNullableValue(ordinal);
- }
-
- public static object? GetNullableValue(this DbDataReader reader, int ordinal) =>
- reader.IsDBNull(ordinal) ? null : reader[ordinal];
-
- public static T? GetNullableValue(this DbDataReader reader, string columnName)
- {
- var ordinal = reader.GetOrdinal(columnName);
- return reader.GetNullableValue(ordinal);
- }
-
- public static T? GetNullableValue(this DbDataReader reader, int ordinal)
- {
- var val = GetNullableValue(reader, ordinal);
- return val == null ? (T?)val : reader.GetFieldValue(ordinal);
- }
-
- ///
- /// because why won't it just auto convert it to a
- ///
- ///
- public static void AddWithNullableValue(this SqliteParameterCollection parameters, string name, object? value)
- => parameters.AddWithValue(name, value ?? DBNull.Value);
-
- #endregion
-
- #region strings
-
- public static string Prefix(this string s, string txt) => $"{txt}{s}";
-
- public static List Prefix(this IEnumerable strings, string txt) =>
- strings.Select(s => s.Prefix(txt)).ToList();
-
- public static string Surround(this string? s, string txt) => $"{txt}{s}{txt}";
-
-
- /// surrounds each string within a list with another string.
- ///
- /// the list strings to be surrounded.
- ///
- /// The text to surround the strings with.
- ///
- /// A list of strings with the parameter txt surrounding each string in the original list.
- public static List Surround(this IEnumerable strings, string txt) => strings.Select(s => s.Surround(txt)).ToList();
-
- public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
- public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s);
- public static bool IsBlank(this string? s) => s == null || string.IsNullOrEmpty(s) || string.IsNullOrWhiteSpace(s);
-
- #endregion
-
- #region Table
-
- ///
- /// add column values to the table based on conditions
- ///
- /// the table to append column values
- /// a key-value pair of column condition and column values; if the column condition is true, the column value will be added to the table.
- /// if the column condition is false, the column (value) will be skipped
- public static Table AddTableRow(this Table table, params KeyValuePair[] columns)
- {
- var row = columns.Where(col => col.Key)
- .Select(col => col.Value.GetPrintValue(allowBlank: true))
- .ToArray();
- table.AddRow(row);
- return table;
- }
-
- public static Table AddTableColumn(this Table table, string name, bool nowrap = false) =>
- table.AddColumn(RenderableFactory.CreateTableColumn(name, nowrap));
-
- public static Table AddTableColumn(this Table table, bool nowrap = false, params string[] columns)
- {
- foreach(var column in columns)
- table.AddTableColumn(column, nowrap);
- return table;
- }
-
- public static Table AddTableColumn(this Table table, params string[] columns)
- {
- foreach(var column in columns)
- table.AddTableColumn(column);
- return table;
- }
-
- #endregion
+
+ public static string GetPrintValue(this string? value, bool allowBlank = false)
+ {
+ if(allowBlank)
+ return value ?? "";
+ return string.IsNullOrWhiteSpace(value) ? Globals.NullString : value;
+ }
+
+ public static string GetPrintValue(this object? value, bool allowBlank = false) =>
+ GetPrintValue(value?.ToString(), allowBlank);
+
+ #region conversions
+
+ public static int ToInt(this ExitCode exitCode) => (int)exitCode;
+ public static DateOnly ToDateOnly(this DateTime date) => DateOnly.FromDateTime(date);
+
+ #endregion
+
+ #region sql values
+
+ public static object? GetNullableValue(this DbDataReader reader, string columnName)
+ {
+ var ordinal = reader.GetOrdinal(columnName);
+ return reader.GetNullableValue(ordinal);
+ }
+
+ public static object? GetNullableValue(this DbDataReader reader, int ordinal) =>
+ reader.IsDBNull(ordinal) ? null : reader[ordinal];
+
+ public static T? GetNullableValue(this DbDataReader reader, string columnName)
+ {
+ var ordinal = reader.GetOrdinal(columnName);
+ return reader.GetNullableValue(ordinal);
+ }
+
+ public static T? GetNullableValue(this DbDataReader reader, int ordinal)
+ {
+ var val = GetNullableValue(reader, ordinal);
+ return val == null ? (T?)val : reader.GetFieldValue(ordinal);
+ }
+
+ ///
+ /// because why won't it just auto convert it to a
+ ///
+ ///
+ public static void AddWithNullableValue(this SqliteParameterCollection parameters, string name, object? value)
+ => parameters.AddWithValue(name, value ?? DBNull.Value);
+
+ #endregion
+
+ #region strings
+
+ public static string Prefix(this string s, string txt) => $"{txt}{s}";
+
+ public static List Prefix(this IEnumerable strings, string txt) =>
+ strings.Select(s => s.Prefix(txt)).ToList();
+
+ public static string Surround(this string? s, string txt) => $"{txt}{s}{txt}";
+
+
+ /// surrounds each string within a list with another string.
+ ///
+ /// the list strings to be surrounded.
+ ///
+ /// The text to surround the strings with.
+ ///
+ /// A list of strings with the parameter txt surrounding each string in the original list.
+ public static List Surround(this IEnumerable strings, string txt) =>
+ strings.Select(s => s.Surround(txt)).ToList();
+
+ public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
+ public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s);
+
+ ///
+ /// check if a string is null, empty, or whitespace
+ ///
+ /// string to check
+ /// true if the string is null, empty, or whitespace; otherwise false
+ public static bool IsBlank(this string? s) => s == null || string.IsNullOrEmpty(s) || string.IsNullOrWhiteSpace(s);
+
+ #endregion
+
+ #region Table
+
+ ///
+ /// add column values to the table based on conditions
+ ///
+ /// the table to append column values
+ /// a key-value pair of column condition and column values; if the column condition is true, the column value will be added to the table.
+ /// if the column condition is false, the column (value) will be skipped
+ public static Table AddTableRow(this Table table, params KeyValuePair[] columns)
+ {
+ var row = columns.Where(col => col.Key)
+ .Select(col => col.Value.GetPrintValue(allowBlank: true))
+ .ToArray();
+ table.AddRow(row);
+ return table;
+ }
+
+ public static Table AddTableColumn(this Table table, string name, bool nowrap = false) =>
+ table.AddColumn(RenderableFactory.CreateTableColumn(name, nowrap));
+
+ public static Table AddTableColumn(this Table table, bool nowrap = false, params string[] columns)
+ {
+ foreach(var column in columns)
+ table.AddTableColumn(column, nowrap);
+ return table;
+ }
+
+ public static Table AddTableColumn(this Table table, params string[] columns)
+ {
+ foreach(var column in columns)
+ table.AddTableColumn(column);
+ return table;
+ }
+
+ #endregion
}
public static class RenderableFactory
{
- public static TableColumn CreateTableColumn(string name, bool nowrap = false) => new TableColumn(name)
- {
- Footer = new Text(name),
- Header = new Text(name),
- NoWrap = nowrap
- };
+ public static TableColumn CreateTableColumn(string name, bool nowrap = false) => new TableColumn(name)
+ {
+ Footer = new Text(name),
+ Header = new Text(name),
+ NoWrap = nowrap
+ };
+
+
+ ///
+ /// The default value of the prompt.
+ /// If the default value is null and allowEmpty is false, the default value will not be displayed (empty brackets) nor set.
+ public static TextPrompt CreateTextPrompt(string prompt, T defaultValue, bool allowEmpty = false,
+ Func? validator = null)
+ {
+ if(defaultValue == null && !allowEmpty)
+ return CreateTextPrompt(prompt, allowEmpty, validator);
+ return CreateTextPrompt(prompt, allowEmpty, validator).DefaultValue(defaultValue);
+ }
+
+ ///
+ /// Creates a text prompt with the specified prompt message, default value, and optional parameters.
+ ///
+ ///
+ /// The type of the prompt result.
+ /// The prompt message to display to the user.
+ /// Optional. Specifies whether empty input is allowed. Defaults to false.
+ /// Optional. A function that validates the input value. Defaults to null.
+ ///
+ /// A object representing the created text prompt.
+ public static TextPrompt CreateTextPrompt(string prompt, bool allowEmpty = false,
+ Func? validator = null) =>
+ new(prompt)
+ {
+ AllowEmpty = allowEmpty,
+ Validator = validator,
+ ShowDefaultValue = false
+ };
}
\ No newline at end of file
diff --git a/resume builder/cli/commands/add/AddJobCommand.cs b/resume builder/cli/commands/add/AddJobCommand.cs
index a3a7197..3fe755c 100644
--- a/resume builder/cli/commands/add/AddJobCommand.cs
+++ b/resume builder/cli/commands/add/AddJobCommand.cs
@@ -1,7 +1,5 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
-using Microsoft.EntityFrameworkCore;
-using resume_builder.models;
using resume_builder.models;
using Spectre.Console;
using Spectre.Console.Cli;
@@ -9,7 +7,7 @@
namespace resume_builder.cli.commands.add;
-internal sealed class AddJobCommand : Command
+internal sealed class AddJobCommand: Command
{
public override int Execute([NotNull] CommandContext context, [NotNull] AddJobSettings settings)
{
@@ -23,70 +21,55 @@ public override int Execute([NotNull] CommandContext context, [NotNull] AddJobSe
if(settings.PromptUser)
{
- // todo: show entered fields as default
- var jobTitlePrompt = new TextPrompt("Job title: ")
- {
- ShowDefaultValue = false,
- AllowEmpty = false,
- Validator = value => string.IsNullOrWhiteSpace(value)
+ var jobTitlePrompt = RenderableFactory.CreateTextPrompt("Job title: ", jobTitle).Validate(value =>
+ string.IsNullOrWhiteSpace(value)
? ValidationResult.Error("Job title is invalid: cannot be empty")
- : ValidationResult.Success(),
- };
- var descriptionPrompt = new TextPrompt("Description: ").ShowDefaultValue(false).AllowEmpty();
- var experiencePrompt = new TextPrompt("Experience: ").ShowDefaultValue(false).AllowEmpty();
- var companyPrompt = new TextPrompt("Company: ").ShowDefaultValue(false).AllowEmpty();
- var endDatePrompt = new TextPrompt("End date: ")
- .AllowEmpty()
- .DefaultValue(null)
- .ShowDefaultValue(false)
- .Validate(date => date > startDate
- ? ValidationResult.Success()
- : ValidationResult.Error("[red]End date must be after start date[/]"));
+ : ValidationResult.Success());
+ var startDatePrompt = RenderableFactory.CreateTextPrompt("Start date: ", startDate ?? Today, true)
+ .Validate(date => date < Today
+ ? ValidationResult.Error("[red]Start date must be in the past[/]")
+ : ValidationResult.Success());
+ var descriptionPrompt = RenderableFactory.CreateTextPrompt("Description: ", jobDescription);
+ var experiencePrompt = RenderableFactory.CreateTextPrompt("Experience: ", experience);
+ var companyPrompt = RenderableFactory.CreateTextPrompt("Company: ", company);
+ var endDatePrompt = RenderableFactory.CreateTextPrompt("End date: ", endDate, true)
+ .Validate(date => date > startDate
+ ? ValidationResult.Success()
+ : ValidationResult.Error("[red]End date must be after start date[/]"));
jobTitle = AnsiConsole.Prompt(jobTitlePrompt);
jobDescription = AnsiConsole.Prompt(descriptionPrompt);
experience = AnsiConsole.Prompt(experiencePrompt);
company = AnsiConsole.Prompt(companyPrompt);
- startDate = AnsiConsole.Ask("Start date: ", Today);
+ startDate = AnsiConsole.Prompt(startDatePrompt);
endDate = AnsiConsole.Prompt(endDatePrompt);
}
if(string.IsNullOrWhiteSpace(jobTitle))
- return PrintError(ExitCode.InvalidArgument, "Job title is invalid, it cannot be empty");
+ throw new ArgumentException("Job title is invalid, it cannot be empty");
- try
- {
- var job = new Job()
- {
- Title = jobTitle,
- Description = jobDescription,
- Experience = experience,
- Company = company,
- StartDate = (DateOnly)startDate!,
- EndDate = endDate
-
- };
- ResumeContext db = new ResumeContext();
- db.Jobs.Add(job);
- db.SaveChanges(acceptAllChangesOnSuccess: true);
- AnsiConsole.MarkupLine($"✅ Job \"[bold]{job.Title}[/]\" added");
- return ExitCode.Success.ToInt();
- }
- catch(Exception e)
+ var job = new Job()
{
- return PrintError(settings, e);
- }
+ Title = jobTitle,
+ Description = jobDescription,
+ Experience = experience,
+ Company = company,
+ StartDate = (DateOnly)startDate!,
+ EndDate = endDate
+ };
+ var db = new ResumeContext();
+ db.Jobs.Add(job);
+ db.SaveChanges(acceptAllChangesOnSuccess: true);
+ AnsiConsole.MarkupLine($"✅ Job \"[bold]{job.Title}[/]\" added");
+ return ExitCode.Success.ToInt();
}
}
-public class AddJobSettings : AddCommandSettings
+
+public class AddJobSettings: AddCommandSettings
{
public bool PromptUser =>
- (JobTitle.IsBlank() && Company.IsBlank() && StartDate == null && EndDate == null &&
- JobDescription.IsBlank() && Experience.IsBlank())
- ||
- //? why did i do this? shouldn't only be if they're all null???
- (!JobTitle.IsBlank() && !Company.IsBlank() && StartDate != null && EndDate != null &&
- !JobDescription.IsBlank() && !Experience.IsBlank())
+ (JobTitle.IsBlank() && Company.IsBlank() && StartDate == null && EndDate == null && JobDescription.IsBlank() &&
+ Experience.IsBlank())
||
Interactive;
@@ -116,13 +99,13 @@ public class AddJobSettings : AddCommandSettings
public override ValidationResult Validate()
{
- if(string.IsNullOrWhiteSpace(JobTitle) && JobTitle != null ||
- (string.IsNullOrWhiteSpace(JobTitle) && !PromptUser))
+ if(string.IsNullOrWhiteSpace(JobTitle) && !PromptUser)
return ValidationResult.Error("Job title is invalid: cannot be empty");
+ // equivalent to
+ //(StartDate != null && EndDate != null && EndDate < StartDate)
return EndDate < StartDate
- ? //(StartDate != null && EndDate < StartDate)
- ValidationResult.Error($"end date must be after start date: {EndDate} < {StartDate}")
+ ? ValidationResult.Error($"end date must be after start date: {EndDate} !< {StartDate}")
: ValidationResult.Success();
}
-}
+}
\ No newline at end of file