diff --git a/Action/.env-example b/Action/.env-example index 843e666..99bec81 100644 --- a/Action/.env-example +++ b/Action/.env-example @@ -9,4 +9,5 @@ License=all-rights-reserved PublicationId=123456789 PublicationName=Philips Tech Blog NotifyFollowers=true -File= \ No newline at end of file +File= +ParseFrontmatter=false \ No newline at end of file diff --git a/Action/PostMediumGitHubAction.csproj b/Action/PostMediumGitHubAction.csproj index 3ccf70a..46dd07a 100644 --- a/Action/PostMediumGitHubAction.csproj +++ b/Action/PostMediumGitHubAction.csproj @@ -11,7 +11,9 @@ + + \ No newline at end of file diff --git a/Action/Properties/launchSettings.json b/Action/Properties/launchSettings.json index 90a8416..66ada68 100644 --- a/Action/Properties/launchSettings.json +++ b/Action/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "PostMediumGitHubAction2": { + "PostMediumGitHubAction": { "commandName": "Project" }, "Docker": { diff --git a/Action/Services/ConfigureService.cs b/Action/Services/ConfigureService.cs index 44b8059..0a07d74 100644 --- a/Action/Services/ConfigureService.cs +++ b/Action/Services/ConfigureService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using CommandLine; @@ -7,6 +8,9 @@ namespace PostMediumGitHubAction.Services { internal class ConfigureService { + public ConfigureService() + { + } public ConfigureService(string[] args) { ConfigureApplication(args); @@ -31,6 +35,7 @@ private void ConfigureApplication(string[] args) Program.Settings.PublicationName = Environment.GetEnvironmentVariable(nameof(Program.Settings.PublicationName)); Program.Settings.PublishStatus = Environment.GetEnvironmentVariable(nameof(Program.Settings.PublishStatus)); + Program.Settings.ParseFrontmatter = Convert.ToBoolean(Environment.GetEnvironmentVariable(nameof(Program.Settings.ParseFrontmatter))); Program.Settings.Tags = Environment.GetEnvironmentVariable(nameof(Program.Settings.Tags))?.Split(','); Program.Settings.Title = Environment.GetEnvironmentVariable(nameof(Program.Settings.Title)); @@ -53,6 +58,42 @@ private void ConfigureApplication(string[] args) Program.Settings.ContentFormat = Program.Settings.ContentFormat?.ToLower(); } + /// + /// Override Program Settings with values from parameter if they are not null. + /// + /// Settings to replace Program settings with + public void OverrideSettings(Settings settingsToReplace) + { + if (settingsToReplace.CanonicalUrl != null) + { + Program.Settings.CanonicalUrl = settingsToReplace.CanonicalUrl; + } + + if (settingsToReplace.ContentFormat != null) + { + Program.Settings.ContentFormat = settingsToReplace.ContentFormat; + } + + if (settingsToReplace.Tags.Any()) + { + Program.Settings.Tags = settingsToReplace.Tags; + } + + if (settingsToReplace.License != null) + { + Program.Settings.License = settingsToReplace.License; + } + + if (settingsToReplace.PublishStatus != null) + { + Program.Settings.PublishStatus = settingsToReplace.PublishStatus; + } + + if (settingsToReplace.Title != null) + { + Program.Settings.Title = settingsToReplace.Title; + } + } /// /// Checks if settings are filled in correctly. /// diff --git a/Action/Services/MediumService.cs b/Action/Services/MediumService.cs index 5306ebb..2cb2c44 100644 --- a/Action/Services/MediumService.cs +++ b/Action/Services/MediumService.cs @@ -5,12 +5,19 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Markdig; +using Markdig.Extensions.Yaml; +using Markdig.Renderers; +using Markdig.Syntax; using PostMediumGitHubAction.Domain; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace PostMediumGitHubAction.Services { public class MediumService { + private ConfigureService _configureService = new ConfigureService(); public async Task SubmitNewContentAsync() { User user = await GetCurrentMediumUserAsync(); @@ -20,11 +27,56 @@ public async Task SubmitNewContentAsync() if (!string.IsNullOrEmpty(Program.Settings.File)) Program.Settings.Content = await ReadFileFromPath(Program.Settings.File); + if (Program.Settings.ContentFormat == "markdown" && Program.Settings.ParseFrontmatter) + { + await ParseFrontmatter(Program.Settings.Content); + } MediumCreatedPost post = await CreateNewPostUnderPublicationAsync(pub.Id); SetWorkflowOutputs(post); } + /// + /// Parse markdown content and look for frontmatter. + /// Convert the markdown into HTML to remove the frontmatter. + /// + /// Content in markdown + /// + private async Task ParseFrontmatter(string content) + { + MarkdownPipeline pipeline = new MarkdownPipelineBuilder() + .UseYamlFrontMatter() + .Build(); + + StringWriter writer = new StringWriter(); + HtmlRenderer renderer = new HtmlRenderer(writer); + pipeline.Setup(renderer); + + MarkdownDocument document = Markdown.Parse(content, pipeline); + + // extract the front matter from markdown document + YamlFrontMatterBlock yamlBlock = document.Descendants().FirstOrDefault(); + + if (yamlBlock != null) + { + string yaml = yamlBlock.Lines.ToString(); + + // deserialize the yaml block into a custom type + IDeserializer deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + + Settings metadata = deserializer.Deserialize(yaml); + _configureService.OverrideSettings(metadata); + // finally we can render the markdown content as html if necessary + renderer.Render(document); + await writer.FlushAsync(); + string html = writer.ToString(); + Program.Settings.Content = html; + Program.Settings.ContentFormat = "html"; + } + } + /// /// Retrieves current authenticated user /// @@ -79,7 +131,7 @@ public async Task CreateNewPostUnderPublicationAsync(string p Content = Program.Settings.Content, ContentFormat = Program.Settings.ContentFormat, PublishStatus = Program.Settings.PublishStatus, - Tags = (string[])Program.Settings.Tags, + Tags = Program.Settings.Tags as string[], Title = Program.Settings.Title }; HttpResponseMessage response = await Program.Client.PostAsync($"publications/{publicationId}/posts", diff --git a/Action/Settings.cs b/Action/Settings.cs index 78fc2a3..4b5de87 100644 --- a/Action/Settings.cs +++ b/Action/Settings.cs @@ -58,5 +58,9 @@ public class Settings 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; } } \ No newline at end of file diff --git a/README.md b/README.md index a5e57cb..5f75941 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@

(back to top)

## State -> Functional, but work in progress. Use at your own risk. +> Functional ## Usage @@ -34,14 +34,14 @@ The easiest way to use this action is to add the following into your workflow fi 1. Add the following part in your workflow file: - ```yaml + ``` jobs: post-to-medium: name: Post to Medium runs-on: ubuntu-latest steps: - name: Create Medium Post - uses: philips-software/post-to-medium-action@v0.2 + uses: philips-software/post-to-medium-action@v0.3 with: integration_token: "${{ secrets.INTEGRATION_TOKEN }}" content: "content here" @@ -69,6 +69,7 @@ The easiest way to use this action is to add the following into your workflow fi | canonical_url | The canonical URL of the post. If canonicalUrl was not specified in the creation of the post, this field will not be present. | `false` | | | tags | The post’s tags. Provide a comma separated string without spaces. | `true` | | | title | The post's title. | `true` | | +| parse_frontmatter | 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. | `true` | false | ## Outputs diff --git a/action.yaml b/action.yaml index 0afbae5..f279bb6 100644 --- a/action.yaml +++ b/action.yaml @@ -47,6 +47,10 @@ inputs: description: "The post's title." required: true default: "" + parse_frontmatter: + description: "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." + required: false + default: "false" outputs: id: @@ -98,6 +102,8 @@ runs: - ${{ inputs.content }} - '--file' - ${{ inputs.file }} + - '--parse-frontmatter' + - ${{ inputs.parse_frontmatter }} branding: