Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/api/ViGet/JavaProperties.cs
diff options
context:
space:
mode:
authorLeitwolf112026-10-29 09:53:57 +0300
committerLeitwolf112026-10-29 09:53:57 +0300
commit58fa0d19d223aaebf9973ebd446c4bb855351ef9 (patch)
tree6fce1c3d9f2255e80ec92c3040e7280c0ce131ae /apps/api/ViGet/JavaProperties.cs
parentf22418235a0a87bc05b3bf9079fcdefba002d3b2 (diff)
downloadprogmaweb-58fa0d19d223aaebf9973ebd446c4bb855351ef9.tar.gz
Add the Tazel plugin catalog of ViGet to the API
ViGet opens in stages: first only Progmasoft publishes Tazel plugins, later every account. This adds the catalog and the server-side publishing for the first stage; nothing publishes through the API yet. - ViGet/: the catalog of Tazel plugins (in memory, or in PostgreSQL beside the accounts with its own numbered schema steps), the descriptor read from the JAR (META-INF/tazel-plugin.properties, with a reader of the Java properties format, and the ProjectPlugin service entry), and publishing: the publisher must be in ViGet:TazelPublishers (Progmasoft for now) and an existing account of that exact name, a version is never replaced, names that differ only in letter case are refused, and ViGet needs a description, an author and a license. The JAR is kept at dslplugins/tazel/<Publisher>/<Name>/<Version>/<Name>-<Version>.jar below ViGet:ArtifactRoot. - Read endpoints under /api/v1/viget/dslplugins/tazel: the list, a plugin, and index.json, the index Tazel reads. - 'viget publish-tazel-plugin <jar>' and 'viget yank-tazel-plugin' run with the configuration of the service instead of the service. - The account store can find an account by name. The pages, the Nginx routes and the deployment follow.
Diffstat (limited to 'apps/api/ViGet/JavaProperties.cs')
-rw-r--r--apps/api/ViGet/JavaProperties.cs149
1 files changed, 149 insertions, 0 deletions
diff --git a/apps/api/ViGet/JavaProperties.cs b/apps/api/ViGet/JavaProperties.cs
new file mode 100644
index 0000000..e55991e
--- /dev/null
+++ b/apps/api/ViGet/JavaProperties.cs
@@ -0,0 +1,149 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+using System.Globalization;
+using System.Text;
+
+namespace Progmasoft.Progmaweb.Api.ViGet;
+
+/// <summary>Reads the text of a Java <c>.properties</c> file, as <c>java.util.Properties.load</c> does.</summary>
+/// <remarks>
+/// A logical line may continue over several lines with a trailing backslash; <c>#</c> and <c>!</c> start a comment
+/// line; a key ends at the first unescaped <c>=</c>, <c>:</c> or white space; and <c>\t</c>, <c>\n</c>, <c>\r</c>,
+/// <c>\f</c> and <c>\uXXXX</c> are escapes, while a backslash before any other character stands for that character.
+/// A later line with the same key replaces an earlier one.
+/// </remarks>
+internal static class JavaProperties
+{
+ /// <summary>Reads the properties of a text.</summary>
+ /// <param name="text">The text of the file, already decoded.</param>
+ /// <returns>The properties by key.</returns>
+ /// <exception cref="FormatException">A <c>\u</c> escape is not followed by four hexadecimal digits.</exception>
+ public static IReadOnlyDictionary<string, string> Parse(string text)
+ {
+ Dictionary<string, string> properties = new(StringComparer.Ordinal);
+ foreach (string line in LogicalLines(text))
+ {
+ int index = 0;
+ while (index < line.Length && IsBlank(line[index]))
+ {
+ index++;
+ }
+
+ if (index == line.Length || line[index] is '#' or '!')
+ {
+ continue;
+ }
+
+ int keyStart = index;
+ while (index < line.Length && !IsKeyEnd(line[index]))
+ {
+ index += line[index] == '\\' ? 2 : 1;
+ }
+
+ string key = Unescape(line[keyStart..Math.Min(index, line.Length)]);
+ while (index < line.Length && IsBlank(line[index]))
+ {
+ index++;
+ }
+
+ if (index < line.Length && line[index] is '=' or ':')
+ {
+ index++;
+ while (index < line.Length && IsBlank(line[index]))
+ {
+ index++;
+ }
+ }
+
+ properties[key] = Unescape(line[index..]);
+ }
+
+ return properties;
+ }
+
+ /// <summary>Joins the physical lines that a trailing backslash continues.</summary>
+ private static IEnumerable<string> LogicalLines(string text)
+ {
+ string[] lines = text.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n').Split('\n');
+ StringBuilder current = new();
+ bool continuing = false;
+ foreach (string physical in lines)
+ {
+ // A continued line loses the white space it starts with.
+ string line = continuing ? physical.TrimStart(' ', '\t', '\f') : physical;
+ int backslashes = 0;
+ for (int position = line.Length - 1; position >= 0 && line[position] == '\\'; position--)
+ {
+ backslashes++;
+ }
+
+ continuing = backslashes % 2 == 1;
+ current.Append(continuing ? line[..^1] : line);
+ if (!continuing)
+ {
+ yield return current.ToString();
+ current.Clear();
+ }
+ }
+
+ if (current.Length > 0)
+ {
+ yield return current.ToString();
+ }
+ }
+
+ private static bool IsBlank(char character) => character is ' ' or '\t' or '\f';
+
+ private static bool IsKeyEnd(char character) => character is '=' or ':' || IsBlank(character);
+
+ private static string Unescape(string text)
+ {
+ StringBuilder result = new(text.Length);
+ for (int index = 0; index < text.Length; index++)
+ {
+ char character = text[index];
+ if (character != '\\' || index + 1 == text.Length)
+ {
+ result.Append(character);
+ continue;
+ }
+
+ char escaped = text[++index];
+ switch (escaped)
+ {
+ case 't':
+ result.Append('\t');
+ break;
+ case 'n':
+ result.Append('\n');
+ break;
+ case 'r':
+ result.Append('\r');
+ break;
+ case 'f':
+ result.Append('\f');
+ break;
+ case 'u':
+ if (text.Length - index - 1 < 4 ||
+ !int.TryParse(
+ text.AsSpan(index + 1, 4),
+ NumberStyles.AllowHexSpecifier,
+ CultureInfo.InvariantCulture,
+ out int code))
+ {
+ throw new FormatException("A \\u escape needs four hexadecimal digits.");
+ }
+
+ result.Append((char)code);
+ index += 4;
+ break;
+ default:
+ result.Append(escaped);
+ break;
+ }
+ }
+
+ return result.ToString();
+ }
+}