diff --git a/Action/Domain/Settings.cs b/Action/Domain/Settings.cs index 47cc4d9..39fcfb8 100644 --- a/Action/Domain/Settings.cs +++ b/Action/Domain/Settings.cs @@ -1,66 +1,66 @@ using System.Collections.Generic; using CommandLine; -namespace PostMediumGitHubAction.Domain +namespace PostMediumGitHubAction.Domain; + +public class Settings { - public class Settings - { - [Option('e', "title", Required = true, HelpText = "The post's title.")] - public string Title { get; set; } + [Option('e', "title", Required = false, HelpText = "The post's title.")] + public string Title { get; set; } - [Option('a', "tags", Required = true, HelpText = "The post’s tags.", Separator = ',')] - public IEnumerable Tags { get; set; } + [Option('a', "tags", Required = false, HelpText = "The post’s tags.", Separator = ',')] + public IEnumerable Tags { get; set; } - [Option('u', "canonical-url", Required = false, HelpText = "The canonical URL of the post. " + - "If canonicalUrl was not specified in the creation of the post, this field will not be present.")] - public string CanonicalUrl { get; set; } + [Option('u', "canonical-url", Required = false, HelpText = "The canonical URL of the post. " + + "If canonicalUrl was not specified in the creation of the post, this field will not be present.")] + public string CanonicalUrl { get; set; } - [Option('s', "publish-status", Required = false, - HelpText = "The publish status of the post. Valid values are: Draft, Public or Unlisted")] - public string PublishStatus { get; set; } = "draft"; + [Option('s', "publish-status", Required = false, + HelpText = "The publish status of the post. Valid values are: Draft, Public or Unlisted")] + public string PublishStatus { get; set; } - [Option('l', "license", Required = false, HelpText = - "The license of the post. Valid values are all-rights-reserved, " + - "cc-40-by, cc-40-by-sa, cc-40-by-nd, cc-40-by-nc, cc-40-by-nc-nd, cc-40-by-nc-sa, cc-40-zero, public-domain.")] - public string License { get; set; } = "all-rights-reserved"; + [Option('l', "license", Required = false, HelpText = + "The license of the post. Valid values are all-rights-reserved, " + + "cc-40-by, cc-40-by-sa, cc-40-by-nd, cc-40-by-nc, cc-40-by-nc-nd, cc-40-by-nc-sa, cc-40-zero, public-domain.")] + public string License { get; set; } - [Option('p', "publication-id", Required = false, - HelpText = - "The id of the publication the post is being created under. If you do not know the Id, use PublicationName")] - public string PublicationId { get; set; } + [Option('p', "publication-id", Required = false, + HelpText = + "The id of the publication the post is being created under. If you do not know the Id, use PublicationName")] + public string PublicationId { get; set; } - [Option('n', "publication-name", Required = false, - HelpText = - "The name of the publication the post is being created under. Either PublicationName of PublicationId should be set.")] - public string PublicationName { get; set; } + [Option('n', "publication-name", Required = false, + HelpText = + "The name of the publication the post is being created under. Either PublicationName of PublicationId should be set.")] + public string PublicationName { get; set; } - [Option('f', "notify-followers", Required = false, - HelpText = "Whether to notify followers that the user has published.")] - public bool NotifyFollowers { get; set; } = false; + [Option('f', "notify-followers", Required = false, + HelpText = "Whether to notify followers that the user has published.")] + public bool NotifyFollowers { get; set; } - [Option('o', "content-format", Required = true, - HelpText = "The format of the \"content\" field. There are two valid values, \"html\", and \"markdown\"")] - public string ContentFormat { get; set; } + [Option('o', "content-format", Required = true, + HelpText = "The format of the \"content\" field. There are two valid values, \"html\", and \"markdown\"")] + public string ContentFormat { get; set; } - [Option('c', "content", Required = false, HelpText = - "The body of the post, in a valid, semantic, HTML fragment, or Markdown. " + - "Further markups may be supported in the future. " + - "If you want your title to appear on the post page, you must also include it as part of the post content.")] - public string Content { get; set; } + [Option('c', "content", Required = false, HelpText = + "The body of the post, in a valid, semantic, HTML fragment, or Markdown. " + + "Further markups may be supported in the future. " + + "If you want your title to appear on the post page, you must also include it as part of the post content.")] + public string Content { get; set; } - [Option('i', "file", Required = false, - HelpText = - "Path to the file that should be read and used as content. If this is set, this will overrule the Content property.")] - public string File { get; set; } + [Option('i', "file", Required = false, + HelpText = + "Path to the file that should be read and used as content. If this is set, this will overrule the Content property.")] + public string File { get; set; } - [Option('t', "integration-token", Required = true, - HelpText = - "The token that should be used to authenticate in the API. Token can be retrieved at https://medium.com/me/settings under \"Integration Token\"")] - public string IntegrationToken { get; set; } + [Option('t', "integration-token", Required = true, + HelpText = + "The token that should be used to authenticate in the API. Token can be retrieved at https://medium.com/me/settings under \"Integration Token\"")] + public string IntegrationToken { get; set; } - [Option('m', "parse-frontmatter", Required = false, - HelpText = "Should the action read and delete frontmatter in a markdown file. Frontmatter should start with --- and end with ---. Should be on the top of the page. When parsing frontmatter, only markdown is supported and settings will be overwritten if specified in the frontmatter.")] - public bool ParseFrontmatter { get; set; } = false; - } + [Option('m', "parse-frontmatter", Required = false, + HelpText = + "Should the action read and delete frontmatter in a markdown file. Frontmatter should start with --- and end with ---. Should be on the top of the page. When parsing frontmatter, only markdown is supported and settings will be overwritten if specified in the frontmatter.")] + public bool ParseFrontmatter { get; set; } } \ No newline at end of file diff --git a/Action/Services/MediumService.cs b/Action/Services/MediumService.cs index cd3a362..2c11b22 100644 --- a/Action/Services/MediumService.cs +++ b/Action/Services/MediumService.cs @@ -72,7 +72,7 @@ public async Task SubmitNewContentAsync() /// /// Content in markdown /// - private async Task ParseFrontmatter(string content) + public async Task ParseFrontmatter(string content) { MarkdownPipeline pipeline = new MarkdownPipelineBuilder() .UseYamlFrontMatter() @@ -91,22 +91,26 @@ private async Task ParseFrontmatter(string content) { string yaml = yamlBlock.Lines.ToString(); - try + // Define a list of naming conventions to support + INamingConvention[] supportedNamingConventions = new INamingConvention[] { - // deserialize the yaml block into a custom type - IDeserializer deserializer = new DeserializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .IgnoreUnmatchedProperties() - .Build(); - Settings metadata = deserializer.Deserialize(yaml); - _settings = _configureService.OverrideSettings(_settings, metadata); - } - catch (YamlException) + PascalCaseNamingConvention.Instance, + UnderscoredNamingConvention.Instance, + CamelCaseNamingConvention.Instance, + NullNamingConvention.Instance, + HyphenatedNamingConvention.Instance + }; + + foreach (INamingConvention namingConvention in supportedNamingConventions) { + // Deserialize YAML with the current naming convention IDeserializer deserializer = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) + .WithNamingConvention(namingConvention) + .IgnoreUnmatchedProperties() .Build(); Settings metadata = deserializer.Deserialize(yaml); + + // Override settings with the deserialized metadata _settings = _configureService.OverrideSettings(_settings, metadata); } diff --git a/Tests/MediumServiceTests.cs b/Tests/MediumServiceTests.cs index 77b3a5c..f98f6bb 100644 --- a/Tests/MediumServiceTests.cs +++ b/Tests/MediumServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading; @@ -255,7 +256,7 @@ public async Task IConfigureService configureService = new ConfigureService(); string[] args = { - "-t", "validToken", "-e", "some-title", "-a", "tag", "-o", "markdown", "-n", + "-t", "validToken", "-o", "markdown", "-n", "Philips Experience Design Blog", "--file", "test-with-unused-frontmatter.md", "--parse-frontmatter", "true" }; Settings configuredSettings = configureService.ConfigureApplication(args); @@ -280,6 +281,29 @@ public async Task Assert.AreEqual("some-content", post.Title); } + [Test] + public async Task + SubmitNewContentAsync_WithVariousNamingConventionsInFrontmatter_ShouldHaveProperSettingsAndCreatePostAsPublication() + { + IConfigureService configureService = new ConfigureService(); + string[] args = + { + "-t", "validToken", "-o", "markdown", "-n", + "Philips Experience Design Blog", "--file", "test-with-various-naming-conventions.md", + "--parse-frontmatter", "true" + }; + Settings configuredSettings = configureService.ConfigureApplication(args); + configuredSettings.Content = + "---\nTitle: \"Hello World\"\ndate: 2023-03-05T16:44:36+08:00\npublishdate: 2023-03-20T04:44:36+08:00\ncanonical_url: 'https://some-url.com'\nPublishStatus: 'unlisted'\ntags: ['helloworld']\ncomments: true\ndraft: true\n---\nHello world"; + MediumService service = new(configuredSettings, new HttpClient()); + + Assert.DoesNotThrowAsync(async () => await service.ParseFrontmatter(configuredSettings.Content)); + Assert.AreEqual("Hello World", configuredSettings.Title); + Assert.AreEqual("https://some-url.com", configuredSettings.CanonicalUrl); + Assert.AreEqual("unlisted", configuredSettings.PublishStatus); + Assert.AreEqual("helloworld", configuredSettings.Tags.First()); + } + [Test] public void SetWorkflowOutputs_WithMediumPost_ShouldSetOutputs() {