From 2a7bbbe0c2fdd34c51aff9b35357408a7feee0d2 Mon Sep 17 00:00:00 2001 From: Atiq Rahman Date: Fri, 3 Feb 2023 15:31:05 -0800 Subject: [PATCH] Move credentials and options to json config file so that driver/example program can be published to public repositories i.e., github In doing so, - provide a list of patterns in config so that internal / confidential strings can be put there - add namespace 'WPExportApp' to share it with JsonConfig class - add unicode examples: weird char replacements And, - comment 'CustomMetadata' out, for actual applications In addition, - update framework to 7.0 - add Nullable Annotations for System.Linq (ref, https://stackoverflow.com/questions/58670909/nullable-reference-type-information-not-exposed-from-firstordefault) Nits - update Copyright - update dep nuget packages // Initial PR Date: 11-21-2021 --- .github/workflows/dotnet.yml | 2 +- Directory.Build.props | 4 +- .../Goodbye.WordPress.CommandLineTool.csproj | 4 +- Goodbye.WordPress.Example/JsonConfig.cs | 74 ++++++++++++ Goodbye.WordPress.Example/Program.cs | 107 ++++++++++++------ Goodbye.WordPress.Example/README.md | 24 ++++ Goodbye.WordPress/Goodbye.WordPress.csproj | 10 +- README.md | 105 +++++++++++------ 8 files changed, 256 insertions(+), 74 deletions(-) create mode 100644 Goodbye.WordPress.Example/JsonConfig.cs create mode 100644 Goodbye.WordPress.Example/README.md diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 2d2b0fd..8ef9eee 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '7.0.x' - name: NuGet Restore run: dotnet restore - name: Build diff --git a/Directory.Build.props b/Directory.Build.props index 802215f..0f35cd1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0 + net7.0 latest enable @@ -8,7 +8,7 @@ $(MSBuildThisFileDirectory)_artifacts Aaron Bockover - Copyright 2020-2021 Aaron Bockover + Copyright 2020-2023 Aaron Bockover https://github.com/abock/goodbye-wordpress MIT diff --git a/Goodbye.WordPress.CommandLineTool/Goodbye.WordPress.CommandLineTool.csproj b/Goodbye.WordPress.CommandLineTool/Goodbye.WordPress.CommandLineTool.csproj index cd311d5..6eece4b 100644 --- a/Goodbye.WordPress.CommandLineTool/Goodbye.WordPress.CommandLineTool.csproj +++ b/Goodbye.WordPress.CommandLineTool/Goodbye.WordPress.CommandLineTool.csproj @@ -10,7 +10,7 @@ $(PackageDescription) - Note, there is a .NET 6.0 library ("Goodbye.WordPress") that + Note, there is a .NET 7.0 library ("Goodbye.WordPress") that can be integrated easily into a new .NET console application that allows heavy customization through overriding pieces of the export pipeline in WordPressExporterDelegate. @@ -23,5 +23,7 @@ + + \ No newline at end of file diff --git a/Goodbye.WordPress.Example/JsonConfig.cs b/Goodbye.WordPress.Example/JsonConfig.cs new file mode 100644 index 0000000..e1f5e22 --- /dev/null +++ b/Goodbye.WordPress.Example/JsonConfig.cs @@ -0,0 +1,74 @@ +namespace WPExportApp { + using System; + using System.Threading.Tasks; + + /// + /// Credentials Related + /// Provides + /// - Credentials + /// - Additional options i.e., for SSL + /// - Patterns for substitution/pre-processing content + /// + class JsonConfig { + public WPExpJsonConfig? Options { get; set; } + + /// + /// Config file path + /// + private string JsonConfigFilePath { get; set; } + + /// + /// + /// Accessing Local App Data via Environment + /// + /// + public JsonConfig() { + JsonConfigFilePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + + @"\WPExportConfig.json"; + + if (!System.IO.File.Exists(JsonConfigFilePath)) { + throw new InvalidOperationException($"Required config: {JsonConfigFilePath} not found!" + + "Please create the config file and run this application again."); + } + } + + public class ReplacePattern { + public string Needle { get; set; } + public string Substitute { get; set; } + } + + /// + /// Structure to read records from config file (json format) + /// Self explanatory props + /// + public class WPExpJsonConfig { + public string Host { get; set; } + public string Database { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string TlsVersion { get; set; } + public string ContentOutputDirectory { get; set; } + public string ArchiveOutputFilePath { get; set; } + /// + /// Some of the patterns might be internal only and should not be published online + /// Therefore, it might be good idea to keep them in the json config file. + /// + public ReplacePattern[] Patterns { get; set; } + } + + + /// + /// Read and Parse Config file + /// + public async Task Load() { + using System.IO.FileStream openStream = System.IO.File.OpenRead(JsonConfigFilePath); + Options = await System.Text.Json.JsonSerializer.DeserializeAsync(openStream); + + if (Options == null || string.IsNullOrEmpty(Options.Host) || string.IsNullOrEmpty( + Options.Database) || string.IsNullOrEmpty(Options.Username) || string.IsNullOrEmpty( + Options.Password)) { + throw new NullReferenceException("Database Credentials are empty in Json config file!"); + } + } + } +} diff --git a/Goodbye.WordPress.Example/Program.cs b/Goodbye.WordPress.Example/Program.cs index 5c78484..b6d588b 100644 --- a/Goodbye.WordPress.Example/Program.cs +++ b/Goodbye.WordPress.Example/Program.cs @@ -1,41 +1,82 @@ -using Goodbye.WordPress; +namespace WPExportApp { + using System.Threading.Tasks; + using Goodbye.WordPress; -var exporter = WordPressExporter.Create( - postReader: new MysqlPostReader(new ConnectionStringBuilder + class WPExportMain { + /// + /// Entry Point + /// + /// CLA + static async Task Main(string[] args) + { + var config = new JsonConfig(); + await config.Load(); + if (config.Options == null) + throw new System.NullReferenceException(); + + var exporter = WordPressExporter.Create( + postReader: new MysqlPostReader( + new ConnectionStringBuilder { + Host = config.Options.Host, + Database = config.Options.Database, + Username = config.Options.Username, + Password = config.Options.Password + // , TlsVersion = config.Options.TlsVersion + }), + contentOutputDirectory: config.Options.ContentOutputDirectory, + archiveOutputFilePath: config.Options.ArchiveOutputFilePath, + // And now the delegate... + @delegate: new CustomExporterDelegate(config.Options.Patterns) + ); + + await exporter.ExportAsync(); + } + } + + + sealed class CustomExporterDelegate : WordPressExporterDelegate { - Host = "localhost", - Username = "user", - Password = "***", - Database = "wordpressdb" - }), - contentOutputDirectory: "exported-posts", - archiveOutputFilePath: "exported-posts/archive.json", - - // And now the delegate... - @delegate: new CustomExporterDelegate()); - -await exporter.ExportAsync(); - -sealed class CustomExporterDelegate : WordPressExporterDelegate -{ - /// Process post contents - public override Post ProcessPost( - WordPressExporter exporter, - Post post) + JsonConfig.ReplacePattern[] StrPatterns; + + public CustomExporterDelegate(JsonConfig.ReplacePattern[] patterns) + { + StrPatterns = patterns; + } + + // Replace weird unicode chars + private string SubstituteCommon(string str) => + str.Replace("‘", "'").Replace("’", "'") + .Replace("“", "\"").Replace("”", "\""); + + private string ProcessContent(string content) { + content = SubstituteCommon(content) + .Replace("http://", "https://"); + + if (StrPatterns != null && StrPatterns.Length > 0) + foreach( var pattern in StrPatterns) + content = content.Replace(pattern.Needle, pattern.Substitute); + + return content; + } + + /// Process post contents + public override Post ProcessPost( + WordPressExporter exporter, + Post post) // Perform the default post processing first by calling base => base.ProcessPost(exporter, post) with { - // Then replace '--' with Unicode em dash '—' - Content = post.Content.Replace("--", "—") + Content = ProcessContent(post.Content) }; - /// Add 'CustomMetadata' to each post's YAML front matter - public override void PopulatePostYamlFrontMatter( - WordPressExporter exporter, - Post post, - SharpYaml.Serialization.YamlMappingNode rootNode) - { - base.PopulatePostYamlFrontMatter(exporter, post, rootNode); - rootNode.Add("CustomMetadata", "Some Value"); + /// Add 'CustomMetadata' to each post's YAML front matter + public override void PopulatePostYamlFrontMatter( + WordPressExporter exporter, + Post post, + SharpYaml.Serialization.YamlMappingNode rootNode) + { + base.PopulatePostYamlFrontMatter(exporter, post, rootNode); + // rootNode.Add("CustomMetadata", "Some Value"); + } } -} +} \ No newline at end of file diff --git a/Goodbye.WordPress.Example/README.md b/Goodbye.WordPress.Example/README.md new file mode 100644 index 0000000..8aede2f --- /dev/null +++ b/Goodbye.WordPress.Example/README.md @@ -0,0 +1,24 @@ +## Example Json Config +Default config path: "$Env:LocalAppData\WPExportConfig.json" + +For example, `C:\Users\UserName\AppData\Local\WPExportConfig.json` + +An example config looks like following, + + { + "Host": "localhost", + "Username": "user", + "Password": "your_password", + "Database": "wordpress_db_name", + "ContentOutputDirectory": "posts", + "ArchiveOutputFilePath": "posts/archive.json", + "Patterns": [ + { + "Needle": "old-domain.com", + "Substitute": "new-domain.com" + } + ] + } + + +For example without json config, please have a look at [primary ReadMe](https://github.com/abock/goodbye-wordpress#example-programcs). \ No newline at end of file diff --git a/Goodbye.WordPress/Goodbye.WordPress.csproj b/Goodbye.WordPress/Goodbye.WordPress.csproj index 634d916..49f17f7 100644 --- a/Goodbye.WordPress/Goodbye.WordPress.csproj +++ b/Goodbye.WordPress/Goodbye.WordPress.csproj @@ -10,10 +10,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 65890a0..92fbf98 100644 --- a/README.md +++ b/README.md @@ -87,45 +87,86 @@ Note that with the exception of `ConnectionStringBuilder` and `WordPressExporter Creating the exporter via API here is equivalent to the command line invocation above, with the exception of providing a custom exporter delegate that will perform additional processing steps. ```csharp -using Goodbye.WordPress; +namespace WPExportApp { + using System.Threading.Tasks; + using Goodbye.WordPress; + + class WPExportMain { + /// + /// Entry Point + /// + /// CLA + static async Task Main(string[] args) + { + var config = new JsonConfig(); + await config.Load(); + if (config.Options == null) + throw new System.NullReferenceException(); + + var exporter = WordPressExporter.Create( + postReader: new MysqlPostReader( + new ConnectionStringBuilder { + Host = config.Options.Host, + Database = config.Options.Database, + Username = config.Options.Username, + Password = config.Options.Password + // , TlsVersion = config.Options.TlsVersion + }), + contentOutputDirectory: config.Options.ContentOutputDirectory, + archiveOutputFilePath: config.Options.ArchiveOutputFilePath, + // And now the delegate... + @delegate: new CustomExporterDelegate(config.Options.Patterns) + ); + + await exporter.ExportAsync(); + } + } -var exporter = WordPressExporter.Create( - postReader: new MysqlPostReader(new ConnectionStringBuilder + + sealed class CustomExporterDelegate : WordPressExporterDelegate { - Host = "localhost", - Username = "user", - Password = "***", - Database = "wordpressdb" - }), - contentOutputDirectory: "exported-posts", - archiveOutputFilePath: "exported-posts/archive.json", - - // And now the delegate... - @delegate: new CustomExporterDelegate()); - -await exporter.ExportAsync(); - -sealed class CustomExporterDelegate : WordPressExporterDelegate -{ - /// Process post contents - public override Post ProcessPost( - WordPressExporter exporter, - Post post) + JsonConfig.ReplacePattern[] StrPatterns; + + public CustomExporterDelegate(JsonConfig.ReplacePattern[] patterns) + { + StrPatterns = patterns; + } + + // Replace weird unicode chars + private string SubstituteCommon(string str) => + str.Replace("‘", "'").Replace("’", "'") + .Replace("“", "\"").Replace("”", "\""); + + private string ProcessContent(string content) { + content = SubstituteCommon(content) + .Replace("http://", "https://"); + + if (StrPatterns != null && StrPatterns.Length > 0) + foreach( var pattern in StrPatterns) + content = content.Replace(pattern.Needle, pattern.Substitute); + + return content; + } + + /// Process post contents + public override Post ProcessPost( + WordPressExporter exporter, + Post post) // Perform the default post processing first by calling base => base.ProcessPost(exporter, post) with { - // Then replace '--' with Unicode em dash '—' - Content = post.Content.Replace("--", "—") + Content = ProcessContent(post.Content) }; - /// Add 'CustomMetadata' to each post's YAML front matter - public override void PopulatePostYamlFrontMatter( - WordPressExporter exporter, - Post post, - SharpYaml.Serialization.YamlMappingNode rootNode) - { - base.PopulatePostYamlFrontMatter(exporter, post, rootNode); - rootNode.Add("CustomMetadata", "Some Value"); + /// Add 'CustomMetadata' to each post's YAML front matter + public override void PopulatePostYamlFrontMatter( + WordPressExporter exporter, + Post post, + SharpYaml.Serialization.YamlMappingNode rootNode) + { + base.PopulatePostYamlFrontMatter(exporter, post, rootNode); + // rootNode.Add("CustomMetadata", "Some Value"); + } } } ``` \ No newline at end of file