Always use \n, not Environment.NewLine

This commit is contained in:
Jonathan Allen
2024-01-04 14:37:29 -07:00
parent 96c47faeb4
commit 9354eedb58
15 changed files with 145 additions and 48 deletions

35
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Management.Web/bin/Debug/net8.0/Management.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/Management.Web",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/canvasManagement.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/canvasManagement.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/canvasManagement.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,5 +1,8 @@
using System.Configuration;
using LocalModels; using LocalModels;
using Management.Services; using Management.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute; using NSubstitute;
using NUnit.Framework.Internal; using NUnit.Framework.Internal;
@@ -37,11 +40,16 @@ public class FileStorageTests
var fileManagerLogger = new MyLogger<FileStorageManager>(NullLogger<FileStorageManager>.Instance); var fileManagerLogger = new MyLogger<FileStorageManager>(NullLogger<FileStorageManager>.Instance);
var markdownLoaderLogger = new MyLogger<CourseMarkdownLoader>(NullLogger<CourseMarkdownLoader>.Instance); var markdownLoaderLogger = new MyLogger<CourseMarkdownLoader>(NullLogger<CourseMarkdownLoader>.Instance);
var markdownSaverLogger = new MyLogger<MarkdownCourseSaver>(NullLogger<MarkdownCourseSaver>.Instance); var markdownSaverLogger = new MyLogger<MarkdownCourseSaver>(NullLogger<MarkdownCourseSaver>.Instance);
var otherLogger = NullLoggerFactory.Instance.CreateLogger<FileStorageManager>();
Environment.SetEnvironmentVariable("storageDirectory", storageDirectory); Environment.SetEnvironmentVariable("storageDirectory", storageDirectory);
var markdownLoader = new CourseMarkdownLoader(markdownLoaderLogger); var config = new ConfigurationBuilder()
var markdownSaver = new MarkdownCourseSaver(markdownSaverLogger); .AddEnvironmentVariables()
fileManager = new FileStorageManager(fileManagerLogger, markdownLoader, markdownSaver); .Build();
var fileConfiguration = new FileConfiguration(config);
var markdownLoader = new CourseMarkdownLoader(markdownLoaderLogger, fileConfiguration);
var markdownSaver = new MarkdownCourseSaver(markdownSaverLogger, fileConfiguration);
fileManager = new FileStorageManager(fileManagerLogger, markdownLoader, markdownSaver, otherLogger, fileConfiguration);
} }
[Test] [Test]
@@ -139,7 +147,9 @@ public class FileStorageTests
var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); var loadedCourses = await fileManager.LoadSavedMarkdownCourses();
var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name);
loadedCourse.Modules.First().Assignments.Should().BeEquivalentTo(testCourse.Modules.First().Assignments); var actualAssignments = loadedCourse.Modules.First().Assignments;
var expectedAssignments = testCourse.Modules.First().Assignments;
actualAssignments.Should().BeEquivalentTo(expectedAssignments);
} }

View File

@@ -32,7 +32,7 @@ lines
Answers = new LocalQuizQuestionAnswer[] Answers = new LocalQuizQuestionAnswer[]
{ {
new LocalQuizQuestionAnswer() { Correct = true, Text = "true" }, new LocalQuizQuestionAnswer() { Correct = true, Text = "true" },
new LocalQuizQuestionAnswer() { Correct = false, Text = "false" + Environment.NewLine +Environment.NewLine + "endline" }, new LocalQuizQuestionAnswer() { Correct = false, Text = "false\n\nendline" },
} }
} }
} }

View File

@@ -12,6 +12,7 @@
@inject CanvasService canvas @inject CanvasService canvas
@inject CoursePlanner planner @inject CoursePlanner planner
@inject NavigationManager navigtion @inject NavigationManager navigtion
@inject IConfiguration config
@code { @code {
[Parameter] [Parameter]
@@ -55,7 +56,7 @@
</button> </button>
<CourseSettings /> <CourseSettings />
<a class="btn btn-outline-secondary" target="_blank" <a class="btn btn-outline-secondary" target="_blank"
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.Settings.CanvasId}")"> href="@($"{config["CANVAS_URL"]}/courses/{planner.LocalCourse.Settings.CanvasId}")">
View In Canvas View In Canvas
</a> </a>
<div class="my-auto ms-2 d-inline"> <div class="my-auto ms-2 d-inline">

View File

@@ -23,14 +23,14 @@ DotEnv.Load();
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var canvas_token = Environment.GetEnvironmentVariable("CANVAS_TOKEN"); var canvas_token = builder.Configuration["CANVAS_TOKEN"];
if (canvas_token == null) if (canvas_token == null)
throw new Exception("CANVAS_TOKEN is null"); throw new Exception("CANVAS_TOKEN is null");
var canvas_url = Environment.GetEnvironmentVariable("CANVAS_URL"); var canvas_url = builder.Configuration["CANVAS_URL"];
if (canvas_url == null) if (canvas_url == null)
{ {
Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com"); Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com");
Environment.SetEnvironmentVariable("CANVAS_URL", "https://snow.instructure.com"); builder.Configuration["CANVAS_URL"] = "https://snow.instructure.com";
} }
const string serviceName = "canvas-management"; const string serviceName = "canvas-management";
@@ -94,6 +94,8 @@ builder.Services.AddScoped<AssignmentEditorContext>();
builder.Services.AddScoped<QuizEditorContext>(); builder.Services.AddScoped<QuizEditorContext>();
builder.Services.AddScoped<DragContainer>(); builder.Services.AddScoped<DragContainer>();
builder.Services.AddSingleton<FileConfiguration>();
builder.Services.AddSignalR(e => builder.Services.AddSignalR(e =>
{ {
e.MaximumReceiveMessageSize = 102400000; e.MaximumReceiveMessageSize = 102400000;

View File

@@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="microsoft.extensions.configuration.abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="RestSharp" Version="108.0.3" /> <PackageReference Include="RestSharp" Version="108.0.3" />
<PackageReference Include="YamlDotNet" Version="13.1.1" /> <PackageReference Include="YamlDotNet" Version="13.1.1" />

View File

@@ -62,9 +62,9 @@ public record LocalAssignment
var settingsString = input.Split("---")[0]; var settingsString = input.Split("---")[0];
var (name, localAssignmentGroupName, submissionTypes, dueAt, lockAt) = parseSettings(settingsString); var (name, localAssignmentGroupName, submissionTypes, dueAt, lockAt) = parseSettings(settingsString);
var description = input.Split("---" + Environment.NewLine)[1].Split("## Rubric")[0]; var description = input.Split("---\n")[1].Split("## Rubric")[0];
var rubricString = input.Split("## Rubric" + Environment.NewLine)[1]; var rubricString = input.Split("## Rubric\n")[1];
var rubric = ParseRubricMarkdown(rubricString); var rubric = ParseRubricMarkdown(rubricString);
return new LocalAssignment() return new LocalAssignment()
{ {
@@ -100,15 +100,17 @@ public record LocalAssignment
private static List<string> parseSubmissionTypes(string input) private static List<string> parseSubmissionTypes(string input)
{ {
input = input.Replace("\r\n", "\n");
List<string> submissionTypes = new List<string>(); List<string> submissionTypes = new List<string>();
// Define a regular expression pattern to match the bulleted list items // Define a regular expression pattern to match the bulleted list items
string startOfTypePattern = @"- (.+)"; string startOfTypePattern = @"- (.+)";
Regex regex = new Regex(startOfTypePattern); Regex regex = new Regex(startOfTypePattern);
var inputAfterSubmissionTypes = input.Split("SubmissionTypes:" + Environment.NewLine)[1]; var words = input.Split("SubmissionTypes:");
var inputAfterSubmissionTypes = words[1];
string[] lines = inputAfterSubmissionTypes.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); string[] lines = inputAfterSubmissionTypes.Split("\n", StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines) foreach (string line in lines)
{ {
@@ -143,10 +145,10 @@ public record LocalAssignment
var settingsMarkdown = settingsToMarkdown(); var settingsMarkdown = settingsToMarkdown();
var rubricMarkdown = RubricToMarkdown(); var rubricMarkdown = RubricToMarkdown();
var assignmentMarkdown = var assignmentMarkdown =
settingsMarkdown + Environment.NewLine settingsMarkdown + "\n"
+ "---" + Environment.NewLine + Environment.NewLine + "---\n\n"
+ Description + Environment.NewLine + Description + "\n\n"
+ Environment.NewLine + "## Rubric" + Environment.NewLine + Environment.NewLine + "## Rubric\n\n"
+ rubricMarkdown; + rubricMarkdown;
return assignmentMarkdown; return assignmentMarkdown;
@@ -158,7 +160,7 @@ public record LocalAssignment
foreach (var item in Rubric) foreach (var item in Rubric)
{ {
var pointLabel = item.Points > 1 ? "pts" : "pt"; var pointLabel = item.Points > 1 ? "pts" : "pt";
builder.Append($"- {item.Points}{pointLabel}: {item.Label}" + Environment.NewLine); builder.Append($"- {item.Points}{pointLabel}: {item.Label}" + "\n");
} }
return builder.ToString(); return builder.ToString();
} }
@@ -166,14 +168,14 @@ public record LocalAssignment
private string settingsToMarkdown() private string settingsToMarkdown()
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append($"Name: {Name}" + Environment.NewLine); builder.Append($"Name: {Name}" + "\n");
builder.Append($"LockAt: {LockAt}" + Environment.NewLine); builder.Append($"LockAt: {LockAt}" + "\n");
builder.Append($"DueAt: {DueAt}" + Environment.NewLine); builder.Append($"DueAt: {DueAt}" + "\n");
builder.Append($"AssignmentGroupName: {LocalAssignmentGroupName}" + Environment.NewLine); builder.Append($"AssignmentGroupName: {LocalAssignmentGroupName}" + "\n");
builder.Append($"SubmissionTypes:" + Environment.NewLine); builder.Append($"SubmissionTypes:" + "\n");
foreach (var submissionType in SubmissionTypes) foreach (var submissionType in SubmissionTypes)
{ {
builder.Append($"- {submissionType}" + Environment.NewLine); builder.Append($"- {submissionType}" + "\n");
} }
return builder.ToString(); return builder.ToString();
} }
@@ -182,7 +184,7 @@ public record LocalAssignment
{ {
if (rawMarkdown.Trim() == string.Empty) if (rawMarkdown.Trim() == string.Empty)
return []; return [];
var lines = rawMarkdown.Trim().Split(Environment.NewLine); var lines = rawMarkdown.Trim().Split("\n");
var items = lines.Select(parseIndividualRubricItemMarkdown).ToArray(); var items = lines.Select(parseIndividualRubricItemMarkdown).ToArray();
return items; return items;
} }

View File

@@ -38,7 +38,7 @@ public record LocalQuiz
public string ToMarkdown() public string ToMarkdown()
{ {
var questionMarkdownArray = Questions.Select(q => q.ToMarkdown()).ToArray(); var questionMarkdownArray = Questions.Select(q => q.ToMarkdown()).ToArray();
var questionDelimiter = Environment.NewLine + Environment.NewLine + "---" + Environment.NewLine + Environment.NewLine; var questionDelimiter = "\n\n---\n\n";
var questionMarkdown = string.Join(questionDelimiter, questionMarkdownArray); var questionMarkdown = string.Join(questionDelimiter, questionMarkdownArray);
return $@"Name: {Name} return $@"Name: {Name}
@@ -57,7 +57,7 @@ Description: {Description}
public static LocalQuiz ParseMarkdown(string input) public static LocalQuiz ParseMarkdown(string input)
{ {
var splitInput = input.Split("---" + Environment.NewLine); var splitInput = input.Split("---\n");
var settings = splitInput[0]; var settings = splitInput[0];
var quizWithoutQuestions = getQuizWithOnlySettings(settings); var quizWithoutQuestions = getQuizWithOnlySettings(settings);

View File

@@ -14,7 +14,7 @@ public record LocalQuizQuestion
public string ToMarkdown() public string ToMarkdown()
{ {
var answerArray = Answers.Select(getAnswerMarkdown); var answerArray = Answers.Select(getAnswerMarkdown);
var answersText = string.Join(Environment.NewLine, answerArray); var answersText = string.Join("\n", answerArray);
var questionTypeIndicator = QuestionType == "essay" || QuestionType == "short_answer" ? QuestionType : ""; var questionTypeIndicator = QuestionType == "essay" || QuestionType == "short_answer" ? QuestionType : "";
return $@"Points: {Points} return $@"Points: {Points}
@@ -25,7 +25,7 @@ public record LocalQuizQuestion
private string getAnswerMarkdown(LocalQuizQuestionAnswer answer, int index) private string getAnswerMarkdown(LocalQuizQuestionAnswer answer, int index)
{ {
var multilineMarkdownCompatibleText = answer.Text.StartsWith("```") var multilineMarkdownCompatibleText = answer.Text.StartsWith("```")
? Environment.NewLine + answer.Text ? "\n" + answer.Text
: answer.Text; : answer.Text;
if (QuestionType == "multiple_answers") if (QuestionType == "multiple_answers")
@@ -53,7 +53,7 @@ public record LocalQuizQuestion
public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex) public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex)
{ {
var lines = input.Trim().Split(Environment.NewLine); var lines = input.Trim().Split("\n");
var firstLineIsPoints = lines.First().Contains("points: ", StringComparison.CurrentCultureIgnoreCase); var firstLineIsPoints = lines.First().Contains("points: ", StringComparison.CurrentCultureIgnoreCase);
var textHasPoints = lines.Length > 0 var textHasPoints = lines.Length > 0
@@ -84,7 +84,7 @@ public record LocalQuizQuestion
) )
.ToArray() .ToArray()
: linesWithoutAnswers; : linesWithoutAnswers;
var description = string.Join(Environment.NewLine, descriptionLines); var description = string.Join("\n", descriptionLines);
@@ -164,7 +164,7 @@ public record LocalQuizQuestion
} }
if (acc.Count != 0) // Append to the previous line if there is one if (acc.Count != 0) // Append to the previous line if there is one
acc[^1] += Environment.NewLine + line; acc[^1] += "\n" + line;
else else
acc.Add(line); acc.Add(line);

View File

@@ -1,10 +1,11 @@
using Management.Services; using Management.Services;
using Microsoft.Extensions.Configuration;
public class FileConfiguration public class FileConfiguration(IConfiguration config)
{ {
public static string GetBasePath() public string GetBasePath()
{ {
string? storageDirectory = Environment.GetEnvironmentVariable("storageDirectory"); string? storageDirectory = config["storageDirectory"];
var basePath = storageDirectory ?? Path.GetFullPath("../storage"); var basePath = storageDirectory ?? Path.GetFullPath("../storage");
if (!Directory.Exists(basePath)) if (!Directory.Exists(basePath))

View File

@@ -15,7 +15,8 @@ public class FileStorageManager
MyLogger<FileStorageManager> logger, MyLogger<FileStorageManager> logger,
CourseMarkdownLoader courseMarkdownLoader, CourseMarkdownLoader courseMarkdownLoader,
MarkdownCourseSaver saveMarkdownCourse, MarkdownCourseSaver saveMarkdownCourse,
ILogger<FileStorageManager> otherLogger ILogger<FileStorageManager> otherLogger,
FileConfiguration fileConfig
) )
{ {
using var activity = DiagnosticsConfig.Source.StartActivity("loading storage directory"); using var activity = DiagnosticsConfig.Source.StartActivity("loading storage directory");
@@ -23,7 +24,7 @@ public class FileStorageManager
_courseMarkdownLoader = courseMarkdownLoader; _courseMarkdownLoader = courseMarkdownLoader;
_saveMarkdownCourse = saveMarkdownCourse; _saveMarkdownCourse = saveMarkdownCourse;
_otherLogger = otherLogger; _otherLogger = otherLogger;
_basePath = FileConfiguration.GetBasePath(); _basePath = fileConfig.GetBasePath();
this.logger.Log("Using storage directory: " + _basePath); this.logger.Log("Using storage directory: " + _basePath);
} }

View File

@@ -6,10 +6,10 @@ public class CourseMarkdownLoader
private readonly MyLogger<CourseMarkdownLoader> logger; private readonly MyLogger<CourseMarkdownLoader> logger;
private readonly string _basePath; private readonly string _basePath;
public CourseMarkdownLoader(MyLogger<CourseMarkdownLoader> logger) public CourseMarkdownLoader(MyLogger<CourseMarkdownLoader> logger, FileConfiguration fileConfig)
{ {
this.logger = logger; this.logger = logger;
_basePath = FileConfiguration.GetBasePath(); _basePath = fileConfig.GetBasePath();
} }
public async Task<IEnumerable<LocalCourse>> LoadSavedMarkdownCourses() public async Task<IEnumerable<LocalCourse>> LoadSavedMarkdownCourses()
@@ -101,7 +101,7 @@ public class CourseMarkdownLoader
var assignmentPromises = assignmentFiles var assignmentPromises = assignmentFiles
.Select(async filePath => .Select(async filePath =>
{ {
var rawFile = await File.ReadAllTextAsync(filePath); var rawFile = (await File.ReadAllTextAsync(filePath)).Replace("\r\n", "\n");
return LocalAssignment.ParseMarkdown(rawFile); return LocalAssignment.ParseMarkdown(rawFile);
}) })
.ToArray(); .ToArray();
@@ -122,7 +122,7 @@ public class CourseMarkdownLoader
var quizPromises = quizFiles var quizPromises = quizFiles
.Select(async path => .Select(async path =>
{ {
var rawQuiz = await File.ReadAllTextAsync(path); var rawQuiz = (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n");
return LocalQuiz.ParseMarkdown(rawQuiz); return LocalQuiz.ParseMarkdown(rawQuiz);
}); });

View File

@@ -2,10 +2,10 @@ using System.Threading.Tasks.Sources;
using LocalModels; using LocalModels;
namespace Management.Services; namespace Management.Services;
public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger) public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger, FileConfiguration fileConfig)
{ {
private readonly MyLogger<MarkdownCourseSaver> _logger = logger; private readonly MyLogger<MarkdownCourseSaver> _logger = logger;
private readonly string _basePath = FileConfiguration.GetBasePath(); private readonly string _basePath = fileConfig.GetBasePath();
public async Task Save(LocalCourse course, LocalCourse? previouslyStoredCourse) public async Task Save(LocalCourse course, LocalCourse? previouslyStoredCourse)
{ {

View File

@@ -1,19 +1,22 @@
using Microsoft.Extensions.Configuration;
using RestSharp; using RestSharp;
public class WebRequestor : IWebRequestor public class WebRequestor : IWebRequestor
{ {
private string BaseUrl = Environment.GetEnvironmentVariable("CANVAS_URL") + "/api/v1/"; private string BaseUrl = "";
private string token; private string token;
private RestClient client; private RestClient client;
private readonly IConfiguration _config;
public WebRequestor() public WebRequestor(IConfiguration config)
{ {
_config = config;
token = token =
Environment.GetEnvironmentVariable("CANVAS_TOKEN") _config["CANVAS_TOKEN"]
?? throw new Exception("CANVAS_TOKEN not in environment"); ?? throw new Exception("CANVAS_TOKEN not in environment");
BaseUrl = _config["CANVAS_URL"] + "/api/v1/";
client = new RestClient(BaseUrl); client = new RestClient(BaseUrl);
client.AddDefaultHeader("Authorization", $"Bearer {token}"); client.AddDefaultHeader("Authorization", $"Bearer {token}");
} }
public async Task<(T[]?, RestResponse)> GetManyAsync<T>(RestRequest request) public async Task<(T[]?, RestResponse)> GetManyAsync<T>(RestRequest request)