Skip to content

Add plugin support for other templates languages #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1f1cac7
Command line refactoring.
sarahelsaig Dec 6, 2024
9eb4f21
Add -p/--plugin command line option.
sarahelsaig Dec 6, 2024
a8d4f00
Add Microsoft.CodeAnalysis.CSharp.Scripting package.
sarahelsaig Dec 6, 2024
f3420df
Add CSX plugin support.
sarahelsaig Dec 6, 2024
c172b43
Some documentation.
sarahelsaig Dec 6, 2024
aeaa3bd
Access bug fix.
sarahelsaig Dec 7, 2024
31224e0
Add sample and test.
sarahelsaig Dec 7, 2024
11b7fd5
Fix spacing.
sarahelsaig Dec 7, 2024
e7c2a10
Fix test name.
sarahelsaig Dec 7, 2024
8716a94
Fix test for Windows.
sarahelsaig Dec 7, 2024
bd6cfd5
Fix warning NU1507: There are 2 package sources defined in your confi…
sarahelsaig Dec 9, 2024
d634380
Move ProcessPluginsAsync logic into the PluginHelper class in the Abs…
sarahelsaig Dec 9, 2024
132d4f4
Revert unnecessary nuget source name change.
sarahelsaig Dec 31, 2024
e4abfa7
Fix spacing.
sarahelsaig Jan 14, 2025
07798dc
prevent index out of range exception
sarahelsaig Jan 14, 2025
9cf8265
Minor code cleanup.
sarahelsaig Jan 14, 2025
857c41f
Add online plugin support as suggested by @sebastienros.
sarahelsaig Jan 14, 2025
f6e0443
Try out online referencing.
sarahelsaig Jan 15, 2025
1517c35
Try out online referencing.
sarahelsaig Jan 15, 2025
4654924
Add support for importing DLLs with a relative path.
sarahelsaig Jan 15, 2025
3a30702
Move PluginHelper to the main project instead of Abstractions
sarahelsaig Jan 15, 2025
bb7aa16
Improve tip wording.
sarahelsaig Jan 15, 2025
af5a94a
Update README.md
sarahelsaig Jan 16, 2025
85448b9
Merge remote-tracking branch 'origin/main' into plugin
sarahelsaig Jan 16, 2025
20ecc7d
Post-merge rewrite.
sarahelsaig Jan 16, 2025
1c03367
fix pluralization.
sarahelsaig Jan 16, 2025
284e8b3
bug fix
sarahelsaig Jan 16, 2025
ffac438
Delete src/OrchardCoreContrib.PoExtractor/GetCliOptionsResult.cs
hishamco Jan 21, 2025
9ec159c
Update test/OrchardCoreContrib.PoExtractor.Tests/PluginTests.cs
sarahelsaig Jan 22, 2025
dd927ad
Merge remote-tracking branch 'origin/main' into plugin
sarahelsaig Jan 22, 2025
fa7ec12
Add missing partial to type.
sarahelsaig Jan 22, 2025
c5ba5db
Update src/OrchardCoreContrib.PoExtractor/PluginHelper.cs
sarahelsaig Jan 22, 2025
935a85e
Update naming convention in CSX and documentation.
sarahelsaig Jan 22, 2025
04f6630
Simplify referencing assemblies in PluginHelper.ProcessPluginsAsync().
sarahelsaig Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Fluid.Core" Version="2.12.0" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="OrchardCore.DisplayManagement.Liquid" Version="2.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
Expand Down
4 changes: 2 additions & 2 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- Ignore global configuration -->
<clear />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
<add key="OrchardCore" value="https://nuget.cloudsmith.io/orchardcore/preview/v3/index.json" />
</packageSources>
</configuration>
2 changes: 2 additions & 0 deletions OrchardCoreContrib.PoExtractor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
README.md = README.md
NuGet.config = NuGet.config
EndProjectSection
EndProject
Global
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ Specifies the code language to extracts translatable strings from. Default: `C#`

Specifies the template engine to extract the translatable strings from. Default: `Razor` & `Liquid` templates.

- **`-p|--plugin {path or URL to CSX file}`**

Specifies a path to a C# script file which can define further project processors. (You can find an example script [here](test/OrchardCoreContrib.PoExtractor.Tests/PluginTestFiles/BasicJsonLocalizationProcessor.csx).) This can be used to process localization from code languages or template engines not supported by the above options. You can have multiple of this switch in one call to load several plugins at once. If the argument starts with `https://` then it's treated as a web URL and the script at that address is downloaded into memory and executed instead of a local file.

When executing the plugins, all _OrchardCoreContrib.PoExtractor_ assemblies are automatically loaded, and two globals are defined:

- `List<IProjectProcessor> projectProcessors`: Add an instance of your custom `IProjectProcessor` implementation type to this list.
- `List<string> projectFiles`: In the unlikely case that you have to add a new project file type (such as _.fsproj_) add the project file paths to this list.

> [!TIP]
> You can't import NuGet packages in your script file, but you can import local DLL files using the `#r "path/to/package.dll"` directive. The path can be relative to the script file's location so you can import packages from the build directory of the project you are extracting from. This can be especially useful if you launch the tool as using MSBuild as a post-build action. (For remote scripts loaded with a URL, the path can be relative to the current working directory.) For example:
>
> ```csharp
> #r "src/Modules/OrchardCore.Commerce/bin/Debug/net8.0/OrchardCore.Commerce.dll"
> using OrchardCore.Commerce.Constants;
> Console.WriteLine("Imported resource name: {0}", ResourceNames.ShoppingCart);
> ```

## Uninstallation

```powershell
Expand Down
9 changes: 9 additions & 0 deletions src/OrchardCoreContrib.PoExtractor/GetCliOptionsResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace OrchardCoreContrib.PoExtractor;

public class GetCliOptionsResult
{
public string Language { get; set; }
public string TemplateEngine { get; set; }
public string SingleOutputFile { get; set; }
public IList<string> Plugins { get; set; } = new List<string>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@
<ProjectReference Include="..\OrchardCoreContrib.PoExtractor.Liquid\OrchardCoreContrib.PoExtractor.Liquid.csproj" />
<ProjectReference Include="..\OrchardCoreContrib.PoExtractor.Razor\OrchardCoreContrib.PoExtractor.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" />
</ItemGroup>
</Project>
40 changes: 40 additions & 0 deletions src/OrchardCoreContrib.PoExtractor/PluginHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Reflection;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace OrchardCoreContrib.PoExtractor;

public static class PluginHelper
{
public static async Task ProcessPluginsAsync(
IList<string> plugins,
List<IProjectProcessor> projectProcessors,
List<string> projectFiles,
IEnumerable<Assembly> assemblies)
{
var sharedOptions = ScriptOptions.Default.AddReferences(assemblies);

foreach (var plugin in plugins)
{
string code;
ScriptOptions options;

if (plugin.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
code = await new HttpClient().GetStringAsync(plugin);
options = sharedOptions.WithFilePath(Path.Join(
Environment.CurrentDirectory,
Path.GetFileName(new Uri(plugin).AbsolutePath)));
}
else
{
code = await File.ReadAllTextAsync(plugin);
options = sharedOptions.WithFilePath(Path.GetFullPath(plugin));
}

await CSharpScript.EvaluateAsync(code, options, new PluginContext(projectProcessors, projectFiles));
}
}

public record PluginContext(List<IProjectProcessor> projectProcessors, List<string> projectFiles);
}
111 changes: 73 additions & 38 deletions src/OrchardCoreContrib.PoExtractor/Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
using OrchardCoreContrib.PoExtractor.DotNet;
using OrchardCore.Modules;
using OrchardCoreContrib.PoExtractor.DotNet;
using OrchardCoreContrib.PoExtractor.DotNet.CS;
using OrchardCoreContrib.PoExtractor.DotNet.VB;
using OrchardCoreContrib.PoExtractor.Liquid;
using OrchardCoreContrib.PoExtractor.Razor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace OrchardCoreContrib.PoExtractor;

Expand All @@ -15,7 +12,7 @@ public class Program
private static readonly string _defaultLanguage = Language.CSharp;
private static readonly string _defaultTemplateEngine = TemplateEngine.Both;

public static void Main(string[] args)
public static async Task Main(string[] args)
{
if (args.Length < 2 || args.Length > 10 || args.Length % 2 == 1)
{
Expand All @@ -34,9 +31,9 @@ public static void Main(string[] args)
return;
}

(string language, string templateEngine, string singleOutputFile) = GetCliOptions(args);
var options = GetCliOptions(args);

if (language == null || templateEngine == null)
if (options.Language == null || options.TemplateEngine == null)
{
ShowHelp();

Expand All @@ -46,7 +43,7 @@ public static void Main(string[] args)
var projectFiles = new List<string>();
var projectProcessors = new List<IProjectProcessor>();

if (language == Language.CSharp)
if (options.Language == Language.CSharp)
{
projectProcessors.Add(new CSharpProjectProcessor());

Expand All @@ -63,28 +60,34 @@ public static void Main(string[] args)
.OrderBy(f => f));
}

if (templateEngine == TemplateEngine.Both)
if (options.TemplateEngine == TemplateEngine.Both)
{
projectProcessors.Add(new RazorProjectProcessor());
projectProcessors.Add(new LiquidProjectProcessor());
}
else if (templateEngine == TemplateEngine.Razor)
else if (options.TemplateEngine == TemplateEngine.Razor)
{
projectProcessors.Add(new RazorProjectProcessor());
}
else if (templateEngine == TemplateEngine.Liquid)
else if (options.TemplateEngine == TemplateEngine.Liquid)
{
projectProcessors.Add(new LiquidProjectProcessor());
}

var isSingleFileOutput = !string.IsNullOrEmpty(singleOutputFile);
if (options.Plugins.Count > 0)
{
await ProcessPluginsAsync(options.Plugins, projectProcessors, projectFiles);
}

var isSingleFileOutput = !string.IsNullOrEmpty(options.SingleOutputFile);
var localizableStrings = new LocalizableStringCollection();
foreach (var projectFile in projectFiles)
{
var projectPath = Path.GetDirectoryName(projectFile);
var projectBasePath = Path.GetDirectoryName(projectPath) + Path.DirectorySeparatorChar;
var projectRelativePath = projectPath[projectBasePath.Length..];
var rootedProject = projectPath[(projectPath.IndexOf(inputPath) + inputPath.Length + 1)..];
var rootedProject = projectPath == inputPath
? projectPath : projectPath[(projectPath.IndexOf(inputPath) + inputPath.Length + 1)..];
if (IgnoredProject.ToList().Any(p => rootedProject.StartsWith(p)))
{
continue;
Expand Down Expand Up @@ -116,7 +119,7 @@ public static void Main(string[] args)
{
if (localizableStrings.Values.Any())
{
var potPath = Path.Combine(outputPath, singleOutputFile);
var potPath = Path.Combine(outputPath, options.SingleOutputFile);

Directory.CreateDirectory(Path.GetDirectoryName(potPath));

Expand All @@ -128,52 +131,74 @@ public static void Main(string[] args)
}
}

private static (string language, string templateEngine, string singleOutputFile) GetCliOptions(string[] args)
/// <summary>
/// A shortcut to <see cref="PluginHelper.ProcessPluginsAsync"/> that gives the script access to all of the
/// <c>OrchardCoreContrib.PoExtractor.*</c> assemblies.
/// </summary>
public static Task ProcessPluginsAsync(
IList<string> plugins,
List<IProjectProcessor> projectProcessors,
List<string> projectFiles) =>
PluginHelper.ProcessPluginsAsync(plugins, projectProcessors, projectFiles, [
typeof(IProjectProcessor).Assembly, // OrchardCoreContrib.PoExtractor.Abstractions
typeof(ExtractingCodeWalker).Assembly, // OrchardCoreContrib.PoExtractor.DotNet
typeof(CSharpProjectProcessor).Assembly, // OrchardCoreContrib.PoExtractor.DotNet.CS
typeof(VisualBasicProjectProcessor).Assembly, // OrchardCoreContrib.PoExtractor.DotNet.VB
typeof(LiquidProjectProcessor).Assembly, // OrchardCoreContrib.PoExtractor.Liquid
typeof(RazorProjectProcessor).Assembly, // OrchardCoreContrib.PoExtractor.Razor
]);

private static GetCliOptionsResult GetCliOptions(string[] args)
{
var language = _defaultLanguage;
var templateEngine = _defaultTemplateEngine;
string singleOutputFile = null;
var result = new GetCliOptionsResult
{
Language = _defaultLanguage,
TemplateEngine = _defaultTemplateEngine,
SingleOutputFile = null,
};

for (int i = 4; i <= args.Length; i += 2)
{
var item = args[i - 1];
switch (args[i - 2])
{
case "-l":
case "--language":
if (args[i - 1].Equals(Language.CSharp, StringComparison.CurrentCultureIgnoreCase))
if (item.Equals(Language.CSharp, StringComparison.CurrentCultureIgnoreCase))
{
language = Language.CSharp;
result.Language = Language.CSharp;
}
else if (args[i - 1].Equals(Language.VisualBasic, StringComparison.CurrentCultureIgnoreCase))
else if (item.Equals(Language.VisualBasic, StringComparison.CurrentCultureIgnoreCase))
{
language = Language.VisualBasic;
result.Language = Language.VisualBasic;
}
else
{
language = null;
result.Language = null;
}

break;
case "-t":
case "--template":
if (args[i - 1].Equals(TemplateEngine.Razor, StringComparison.CurrentCultureIgnoreCase))
if (item.Equals(TemplateEngine.Razor, StringComparison.CurrentCultureIgnoreCase))
{
templateEngine = TemplateEngine.Razor;
result.TemplateEngine = TemplateEngine.Razor;
}
else if (args[i - 1].Equals(TemplateEngine.Liquid, StringComparison.CurrentCultureIgnoreCase))
else if (item.Equals(TemplateEngine.Liquid, StringComparison.CurrentCultureIgnoreCase))
{
templateEngine = TemplateEngine.Liquid;
result.TemplateEngine = TemplateEngine.Liquid;
}
else
{
templateEngine = null;
result.TemplateEngine = null;
}

break;
case "-i":
case "--ignore":
if (!string.IsNullOrEmpty(args[i - 1]))
if (!string.IsNullOrEmpty(item))
{
var ignoredProjects = args[i - 1].Split(',', StringSplitOptions.RemoveEmptyEntries);
var ignoredProjects = item.Split(',', StringSplitOptions.RemoveEmptyEntries);

foreach (var ignoredProject in ignoredProjects)
{
Expand All @@ -183,30 +208,38 @@ private static (string language, string templateEngine, string singleOutputFile)

break;
case "--localizer":
if (!string.IsNullOrEmpty(args[i - 1]))
if (!string.IsNullOrEmpty(item))
{
var localizerIdentifiers = args[i - 1].Split(',', StringSplitOptions.RemoveEmptyEntries);
var localizerIdentifiers = item.Split(',', StringSplitOptions.RemoveEmptyEntries);

LocalizerAccessors.LocalizerIdentifiers = localizerIdentifiers;
}

break;
case "-s":
case "--single":
if (!string.IsNullOrEmpty(args[i - 1]))
if (!string.IsNullOrEmpty(item))
{
result.SingleOutputFile = item;
}

break;
case "-p":
case "--plugin":
if (File.Exists(item) || item.StartsWithOrdinalIgnoreCase("https://"))
{
singleOutputFile = args[i - 1];
result.Plugins.Add(item);
}

break;
default:
language = null;
templateEngine = null;
result.Language = null;
result.TemplateEngine = null;
break;
}
}

return (language, templateEngine, singleOutputFile);
return result;
}

private static void ShowHelp()
Expand All @@ -226,5 +259,7 @@ private static void ShowHelp()
Console.WriteLine(" -i, --ignore project1,project2 Ignores extracting PO filed from a given project(s).");
Console.WriteLine(" --localizer localizer1,localizer2 Specifies the name of the localizer(s) that will be used during the extraction process.");
Console.WriteLine(" -s, --single <FILE_NAME> Specifies the single output file.");
Console.WriteLine(" -p, --plugin <FILE_NAME_OR_HTTPS_URL> A path or web URL with HTTPS scheme to a C# script (.csx) file which can define further");
Console.WriteLine(" IProjectProcessor implementations. You can have multiple of this switch in a call.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@
<ItemGroup>
<ProjectReference Include="..\..\src\OrchardCoreContrib.PoExtractor\OrchardCoreContrib.PoExtractor.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="PluginTestFiles\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Loading