appendix: 23 Code-Ausschnitte aus dem Houston-Repository
Pin nixpkgs to the revision used by itc.componentware: the channel version ships a latexminted that crashes on Python 3.14, which made every listing fail and sent latexmk into an endless error prompt. Also force nonstopmode/halt-on-error so a broken listing can never loop again.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
namespace Houston.Model.Documents;
|
||||
|
||||
public readonly record struct OrgSlug
|
||||
{
|
||||
public const int MaxLength = 128;
|
||||
|
||||
private const string CharactersToReplace = @"/\{}^%`][""><~#|";
|
||||
|
||||
private const char ReplacementCharacter = '-';
|
||||
|
||||
/// <summary>
|
||||
/// Characters that must not start or end a folder name, because they are either invisible or
|
||||
/// break path semantics on the client side.
|
||||
/// </summary>
|
||||
private static readonly char[] UntrimmableEdgeCharacters = [' ', '.', ReplacementCharacter];
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
private OrgSlug(string value) => Value = value;
|
||||
|
||||
public static bool TryFromS3Key(string key, [NotNullWhen(true)] out OrgSlug? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!key.EndsWith('/')) return false;
|
||||
|
||||
var parts = key.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return parts is [var name] && TryFromFolderName(name, out result);
|
||||
}
|
||||
|
||||
public static bool TryFromFolderName(string name, [NotNullWhen(true)] out OrgSlug? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name)) return false;
|
||||
if (name.Contains('/')) return false;
|
||||
if (name == "..") return false;
|
||||
|
||||
result = new OrgSlug(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the canonical folder name for an organization.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>false</c> if the name contains nothing that survives the normalization, in which case
|
||||
/// there is no folder name Houston could safely claim.
|
||||
/// </returns>
|
||||
public static bool TryFrom(OrgName name, [NotNullWhen(true)] out OrgSlug? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
var slug = Normalize(name.Value, MaxLength);
|
||||
if (slug.Length == 0)
|
||||
return false;
|
||||
|
||||
result = new OrgSlug(slug);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryFrom(OrgName name, OrgId id, [NotNullWhen(true)] out OrgSlug? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
var normalizedId = Normalize(id.Value, MaxLength);
|
||||
if (normalizedId.Length == 0)
|
||||
return false;
|
||||
|
||||
// The id is what makes the folder name unique, so the name is what gets truncated.
|
||||
var suffix = $" ({normalizedId})";
|
||||
if (suffix.Length >= MaxLength)
|
||||
return false;
|
||||
|
||||
var slug = Normalize(name.Value, MaxLength - suffix.Length);
|
||||
|
||||
result = new OrgSlug(slug.Length == 0 ? normalizedId : slug + suffix);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string Normalize(string value, int maxLength)
|
||||
{
|
||||
var builder = new StringBuilder(Math.Min(value.Length, maxLength));
|
||||
|
||||
foreach (var character in value.Trim().TakeWhile(c => builder.Length != maxLength))
|
||||
{
|
||||
builder.Append(Char.IsControl(character) || CharactersToReplace.Contains(character)
|
||||
? ReplacementCharacter
|
||||
: character);
|
||||
}
|
||||
|
||||
// Truncating can uncover a trailing space or dot, so trimming happens afterwards.
|
||||
return builder.ToString().Trim().Trim(UntrimmableEdgeCharacters).Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S3 prefix of the folder, including the trailing slash.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
// A default(OrgSlug) would resolve to the bucket root and expose every customer's
|
||||
// documents, so it must never be used as a prefix.
|
||||
if (String.IsNullOrEmpty(Value))
|
||||
throw new InvalidOperationException("An uninitialized OrgSlug cannot be used as an S3 prefix.");
|
||||
|
||||
return Value + '/';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user