Skip to content

Commit df4aef3

Browse files
committed
new TPS v1.1.0
1 parent 5c4fbe1 commit df4aef3

File tree

48 files changed

+2955
-2170
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2955
-2170
lines changed

design/TPS.md

Lines changed: 693 additions & 0 deletions
Large diffs are not rendered by default.

src/PrompterOne.Core/Editor/Models/TpsStructureHeaderSnapshot.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public sealed record TpsStructureHeaderSnapshot(
1313
string Name,
1414
int? TargetWpm,
1515
string EmotionKey,
16+
string Speaker,
1617
string Timing)
1718
{
1819
public bool SupportsTiming => Kind == TpsStructureHeaderKind.Segment;

src/PrompterOne.Core/Editor/Services/TpsFrontMatterDocumentService.cs

Lines changed: 133 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,30 @@
44

55
namespace PrompterOne.Core.Services.Editor;
66

7-
public sealed class TpsFrontMatterDocumentService
7+
public sealed partial class TpsFrontMatterDocumentService
88
{
9-
private static readonly Regex FrontMatterRegex = new(
10-
@"\A---\s*\r?\n(?<front>.*?)\r?\n---\s*\r?\n?",
11-
RegexOptions.Singleline | RegexOptions.Compiled);
12-
139
private static readonly string[] PreferredMetadataOrder =
1410
[
1511
MetadataKeys.Title,
16-
MetadataKeys.Author,
1712
MetadataKeys.Profile,
13+
MetadataKeys.Duration,
1814
MetadataKeys.BaseWpm,
19-
MetadataKeys.DisplayDuration,
20-
MetadataKeys.XslowOffset,
21-
MetadataKeys.SlowOffset,
22-
MetadataKeys.FastOffset,
23-
MetadataKeys.XfastOffset,
24-
MetadataKeys.Version,
25-
MetadataKeys.Created
15+
MetadataKeys.Author,
16+
MetadataKeys.Created,
17+
MetadataKeys.Version
2618
];
2719

20+
[GeneratedRegex(@"\A---\s*\r?\n(?<front>.*?)\r?\n---\s*\r?\n?", RegexOptions.Singleline)]
21+
private static partial Regex FrontMatterRegex();
22+
2823
public TpsFrontMatterDocument Parse(string? text)
2924
{
3025
if (string.IsNullOrWhiteSpace(text))
3126
{
3227
return new TpsFrontMatterDocument(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase), string.Empty, 0);
3328
}
3429

35-
var match = FrontMatterRegex.Match(text);
30+
var match = FrontMatterRegex().Match(text);
3631
if (!match.Success)
3732
{
3833
return new TpsFrontMatterDocument(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase), text, 0);
@@ -45,25 +40,23 @@ public TpsFrontMatterDocument Parse(string? text)
4540
public string Build(IReadOnlyDictionary<string, string> metadata, string? body)
4641
{
4742
var builder = new StringBuilder();
48-
builder.AppendLine(MetadataTokens.Delimiter);
43+
builder.AppendLine("---");
4944

5045
foreach (var key in PreferredMetadataOrder.Where(metadata.ContainsKey))
5146
{
52-
builder.Append(key);
53-
builder.Append(MetadataTokens.Separator);
54-
builder.AppendLine(FormatMetadataValue(key, metadata[key]));
47+
AppendMetadataEntry(builder, key, metadata[key], IsNumericKey(key));
5548
}
5649

50+
AppendSpeedOffsets(builder, metadata);
51+
5752
foreach (var entry in metadata
58-
.Where(entry => !PreferredMetadataOrder.Contains(entry.Key, StringComparer.OrdinalIgnoreCase))
53+
.Where(entry => !PreferredMetadataOrder.Contains(entry.Key, StringComparer.OrdinalIgnoreCase) && !IsSpeedOffsetKey(entry.Key))
5954
.OrderBy(entry => entry.Key, StringComparer.OrdinalIgnoreCase))
6055
{
61-
builder.Append(entry.Key);
62-
builder.Append(MetadataTokens.Separator);
63-
builder.AppendLine(FormatMetadataValue(entry.Key, entry.Value));
56+
AppendMetadataEntry(builder, entry.Key, entry.Value, IsNumericKey(entry.Key));
6457
}
6558

66-
builder.AppendLine(MetadataTokens.Delimiter);
59+
builder.AppendLine("---");
6760
builder.AppendLine();
6861
builder.Append((body ?? string.Empty).TrimStart());
6962
return builder.ToString();
@@ -95,104 +88,165 @@ public string Upsert(string? text, IReadOnlyDictionary<string, string?> updates)
9588

9689
public string ResolveTitle(string? text, string fallback)
9790
{
98-
var document = Parse(text);
99-
return document.Metadata.TryGetValue(MetadataKeys.Title, out var title) &&
100-
!string.IsNullOrWhiteSpace(title)
101-
? title.Trim()
102-
: fallback;
91+
return ResolveValue(text, MetadataKeys.Title, fallback);
10392
}
10493

10594
public string ResolveValue(string? text, string key, string fallback)
10695
{
10796
var document = Parse(text);
108-
return document.Metadata.TryGetValue(key, out var value) &&
109-
!string.IsNullOrWhiteSpace(value)
97+
return document.Metadata.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)
11098
? value.Trim()
11199
: fallback;
112100
}
113101

114-
private static string FormatMetadataValue(string key, string? value)
115-
{
116-
value ??= string.Empty;
117-
118-
if (IsNumericMetadataKey(key) && int.TryParse(value, out _))
119-
{
120-
return value;
121-
}
122-
123-
return $"{MetadataTokens.Quote}{value.Replace(MetadataTokens.Quote, MetadataTokens.EscapedQuote, StringComparison.Ordinal)}{MetadataTokens.Quote}";
124-
}
125-
126102
public static class MetadataKeys
127103
{
128-
public const string Author = "author";
129-
public const string BaseWpm = "base_wpm";
130-
public const string Created = "created";
131-
public const string DisplayDuration = "display_duration";
132-
public const string FastOffset = "fast_offset";
133-
public const string Profile = "profile";
134-
public const string SlowOffset = "slow_offset";
135-
public const string Title = "title";
136-
public const string Version = "version";
137-
public const string XfastOffset = "xfast_offset";
138-
public const string XslowOffset = "xslow_offset";
104+
public const string Author = TpsSpec.FrontMatterKeys.Author;
105+
public const string BaseWpm = TpsSpec.FrontMatterKeys.BaseWpm;
106+
public const string Created = TpsSpec.FrontMatterKeys.Created;
107+
public const string Duration = TpsSpec.FrontMatterKeys.Duration;
108+
public const string Profile = TpsSpec.FrontMatterKeys.Profile;
109+
public const string SpeedOffsetFast = TpsSpec.FrontMatterKeys.SpeedOffsetsFast;
110+
public const string SpeedOffsetSlow = TpsSpec.FrontMatterKeys.SpeedOffsetsSlow;
111+
public const string SpeedOffsetXfast = TpsSpec.FrontMatterKeys.SpeedOffsetsXfast;
112+
public const string SpeedOffsetXslow = TpsSpec.FrontMatterKeys.SpeedOffsetsXslow;
113+
public const string Title = TpsSpec.FrontMatterKeys.Title;
114+
public const string Version = TpsSpec.FrontMatterKeys.Version;
139115
}
140116

141117
private static Dictionary<string, string> ParseMetadata(string frontMatterText)
142118
{
143119
var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
144120
string? currentSection = null;
145121

146-
foreach (var rawLine in frontMatterText.Split('\n', StringSplitOptions.RemoveEmptyEntries))
122+
foreach (var rawLine in frontMatterText.Split('\n'))
147123
{
148-
var line = rawLine.TrimEnd();
149-
var trimmedLine = line.Trim();
150-
if (string.IsNullOrWhiteSpace(trimmedLine))
124+
if (string.IsNullOrWhiteSpace(rawLine))
151125
{
152126
continue;
153127
}
154128

155-
var separatorIndex = trimmedLine.IndexOf(':');
129+
var indentationLength = rawLine.Length - rawLine.TrimStart().Length;
130+
var line = rawLine.Trim();
131+
var separatorIndex = line.IndexOf(':');
156132
if (separatorIndex <= 0)
157133
{
158134
continue;
159135
}
160136

161-
var key = trimmedLine[..separatorIndex].Trim();
162-
var value = trimmedLine[(separatorIndex + 1)..].Trim().Trim('"', '\'');
163-
var isNestedLine = line.Length != line.TrimStart().Length;
164-
165-
if (isNestedLine && !string.IsNullOrWhiteSpace(currentSection))
137+
var key = line[..separatorIndex].Trim();
138+
var value = TrimValue(line[(separatorIndex + 1)..]);
139+
if (indentationLength > 0 && !string.IsNullOrWhiteSpace(currentSection))
166140
{
167-
metadata[$"{currentSection}.{key}"] = value;
141+
var compositeKey = $"{currentSection}.{key}";
142+
if (!IsLegacyKey(compositeKey))
143+
{
144+
metadata[compositeKey] = value;
145+
}
146+
168147
continue;
169148
}
170149

150+
currentSection = string.IsNullOrWhiteSpace(value) ? key : null;
171151
if (string.IsNullOrWhiteSpace(value))
172152
{
173-
currentSection = key;
174153
continue;
175154
}
176155

177-
currentSection = null;
178-
metadata[key] = value;
156+
if (!IsLegacyKey(key))
157+
{
158+
metadata[key] = value;
159+
}
179160
}
180161

181-
return TpsFrontMatterMetadataNormalizer.Normalize(metadata);
162+
return metadata;
182163
}
183164

184-
private static bool IsNumericMetadataKey(string key) =>
185-
string.Equals(key, MetadataKeys.BaseWpm, StringComparison.OrdinalIgnoreCase) ||
186-
string.Equals(key, MetadataKeys.XslowOffset, StringComparison.OrdinalIgnoreCase) ||
187-
string.Equals(key, MetadataKeys.SlowOffset, StringComparison.OrdinalIgnoreCase) ||
188-
string.Equals(key, MetadataKeys.FastOffset, StringComparison.OrdinalIgnoreCase) ||
189-
string.Equals(key, MetadataKeys.XfastOffset, StringComparison.OrdinalIgnoreCase);
165+
private static void AppendSpeedOffsets(StringBuilder builder, IReadOnlyDictionary<string, string> metadata)
166+
{
167+
var speedOffsetKeys = new[]
168+
{
169+
MetadataKeys.SpeedOffsetXslow,
170+
MetadataKeys.SpeedOffsetSlow,
171+
MetadataKeys.SpeedOffsetFast,
172+
MetadataKeys.SpeedOffsetXfast
173+
};
174+
175+
if (!speedOffsetKeys.Any(metadata.ContainsKey))
176+
{
177+
return;
178+
}
179+
180+
builder.AppendLine("speed_offsets:");
181+
AppendNestedSpeedOffset(builder, metadata, MetadataKeys.SpeedOffsetXslow, TpsSpec.Tags.Xslow);
182+
AppendNestedSpeedOffset(builder, metadata, MetadataKeys.SpeedOffsetSlow, TpsSpec.Tags.Slow);
183+
AppendNestedSpeedOffset(builder, metadata, MetadataKeys.SpeedOffsetFast, TpsSpec.Tags.Fast);
184+
AppendNestedSpeedOffset(builder, metadata, MetadataKeys.SpeedOffsetXfast, TpsSpec.Tags.Xfast);
185+
}
186+
187+
private static void AppendNestedSpeedOffset(StringBuilder builder, IReadOnlyDictionary<string, string> metadata, string key, string nestedKey)
188+
{
189+
if (!metadata.TryGetValue(key, out var value))
190+
{
191+
return;
192+
}
193+
194+
builder.Append(" ");
195+
builder.Append(nestedKey);
196+
builder.Append(": ");
197+
builder.AppendLine(FormatValue(value, numeric: true));
198+
}
199+
200+
private static void AppendMetadataEntry(StringBuilder builder, string key, string value, bool numeric)
201+
{
202+
builder.Append(key);
203+
builder.Append(": ");
204+
builder.AppendLine(FormatValue(value, numeric));
205+
}
206+
207+
private static string TrimValue(string value)
208+
{
209+
var trimmed = value.Trim();
210+
if (trimmed.Length >= 2 &&
211+
((trimmed.StartsWith("\"", StringComparison.Ordinal) && trimmed.EndsWith("\"", StringComparison.Ordinal)) ||
212+
(trimmed.StartsWith("'", StringComparison.Ordinal) && trimmed.EndsWith("'", StringComparison.Ordinal))))
213+
{
214+
return trimmed[1..^1];
215+
}
216+
217+
return trimmed;
218+
}
219+
220+
private static string FormatValue(string value, bool numeric)
221+
{
222+
return numeric && int.TryParse(value, out _)
223+
? value
224+
: $"\"{value.Replace("\"", "\\\"", StringComparison.Ordinal)}\"";
225+
}
226+
227+
private static bool IsNumericKey(string key)
228+
{
229+
return key.Equals(MetadataKeys.BaseWpm, StringComparison.OrdinalIgnoreCase) || IsSpeedOffsetKey(key);
230+
}
231+
232+
private static bool IsSpeedOffsetKey(string key)
233+
{
234+
return key.Equals(MetadataKeys.SpeedOffsetXslow, StringComparison.OrdinalIgnoreCase) ||
235+
key.Equals(MetadataKeys.SpeedOffsetSlow, StringComparison.OrdinalIgnoreCase) ||
236+
key.Equals(MetadataKeys.SpeedOffsetFast, StringComparison.OrdinalIgnoreCase) ||
237+
key.Equals(MetadataKeys.SpeedOffsetXfast, StringComparison.OrdinalIgnoreCase);
238+
}
190239

191-
private static class MetadataTokens
240+
private static bool IsLegacyKey(string key)
192241
{
193-
public const string Delimiter = "---";
194-
public const string EscapedQuote = "\\\"";
195-
public const string Quote = "\"";
196-
public const string Separator = ": ";
242+
return key.Equals(TpsSpec.LegacyKeys.DisplayDuration, StringComparison.OrdinalIgnoreCase) ||
243+
key.Equals(TpsSpec.LegacyKeys.XslowOffset, StringComparison.OrdinalIgnoreCase) ||
244+
key.Equals(TpsSpec.LegacyKeys.SlowOffset, StringComparison.OrdinalIgnoreCase) ||
245+
key.Equals(TpsSpec.LegacyKeys.FastOffset, StringComparison.OrdinalIgnoreCase) ||
246+
key.Equals(TpsSpec.LegacyKeys.XfastOffset, StringComparison.OrdinalIgnoreCase) ||
247+
key.Equals(TpsSpec.LegacyKeys.PresetsXslow, StringComparison.OrdinalIgnoreCase) ||
248+
key.Equals(TpsSpec.LegacyKeys.PresetsSlow, StringComparison.OrdinalIgnoreCase) ||
249+
key.Equals(TpsSpec.LegacyKeys.PresetsFast, StringComparison.OrdinalIgnoreCase) ||
250+
key.Equals(TpsSpec.LegacyKeys.PresetsXfast, StringComparison.OrdinalIgnoreCase);
197251
}
198252
}

src/PrompterOne.Core/Editor/Services/TpsFrontMatterMetadataNormalizer.cs

Lines changed: 0 additions & 74 deletions
This file was deleted.

0 commit comments

Comments
 (0)