Files
Omika/tools/Omika.LanguesProbe/Arguments.cs
T
2026-10-23 18:46:50 +02:00

122 lines
4.6 KiB
C#

namespace Omika.LanguesProbe;
/// <summary>
/// Arguments d'une commande, séparés en positionnels et options nommées.
///
/// <para>
/// L'analyse est volontairement stricte : une option inconnue est <b>refusée</b>
/// au lieu d'être ignorée. Sur un poste de développement, une faute de frappe
/// silencieuse (<c>--dossiers</c>, <c>--sens</c> mal orthographié) ferait
/// travailler l'outil sur autre chose que ce que le développeur croit, et le
/// diagnostic serait faux sans que rien ne le signale.
/// </para>
///
/// <para>
/// Les valeurs ne sont jamais redécoupées : un chemin contenant des espaces
/// arrive en un seul argument, grâce aux guillemets du shell, et le reste.
/// </para>
/// </summary>
internal sealed class Arguments
{
private readonly List<string> _positionnels = [];
private readonly Dictionary<string, string> _options = new(StringComparer.OrdinalIgnoreCase);
private Arguments()
{
}
/// <summary>
/// Analyse les arguments d'une commande.
/// </summary>
/// <param name="args">Arguments suivant le nom de la commande.</param>
/// <param name="optionsAttendues">Options nommées acceptées, sans les tirets.</param>
/// <exception cref="AideDemandeeException"><c>--aide</c> ou <c>-h</c> est présent.</exception>
/// <exception cref="ErreurUsageException">Option inconnue, ou option sans valeur.</exception>
public static Arguments Analyser(string[] args, params string[] optionsAttendues)
{
var resultat = new Arguments();
var connues = new HashSet<string>(optionsAttendues, StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < args.Length; i++)
{
string argument = args[i];
if (argument is "--aide" or "-h")
{
// Reconnu à n'importe quelle position : « traduire --aide » doit expliquer la
// commande, pas réclamer --dossier.
throw new AideDemandeeException();
}
if (!argument.StartsWith("--", StringComparison.Ordinal))
{
resultat._positionnels.Add(SansGuillemets(argument));
continue;
}
string nom = argument[2..];
if (!connues.Contains(nom))
{
throw new ErreurUsageException($"option inconnue « {argument} »");
}
if (i + 1 >= args.Length)
{
throw new ErreurUsageException($"l'option « {argument} » attend une valeur");
}
// La valeur est consommée telle quelle, même si elle commence par « -- » : un texte à
// traduire peut légitimement commencer par un tiret.
resultat._options[nom] = SansGuillemets(args[++i]);
}
return resultat;
}
/// <summary>Argument positionnel, ou erreur d'usage s'il manque.</summary>
public string Positionnel(int index, string description)
=> index < _positionnels.Count
? _positionnels[index]
: throw new ErreurUsageException($"argument manquant : {description}");
/// <summary>Valeur d'une option, ou <see langword="null"/> si elle est absente.</summary>
public string? Option(string nom)
=> _options.TryGetValue(nom, out string? valeur) ? valeur : null;
/// <summary>Valeur d'une option, ou <paramref name="defaut"/> si elle est absente.</summary>
public string Option(string nom, string defaut) => Option(nom) ?? defaut;
/// <summary>Positionnels à partir d'un index (un texte peut arriver en plusieurs arguments).</summary>
public IReadOnlyList<string> Reste(int depuis)
{
if (depuis >= _positionnels.Count)
{
return [];
}
return _positionnels.GetRange(depuis, _positionnels.Count - depuis);
}
/// <summary>
/// Retire une paire de guillemets entourant une valeur : un copier-coller
/// depuis un document, ou un shell qui transmet les guillemets eux-mêmes,
/// produirait sinon un chemin de fichier introuvable.
/// </summary>
private static string SansGuillemets(string valeur)
=> valeur.Length >= 2 && valeur[0] == '"' && valeur[^1] == '"' ? valeur[1..^1] : valeur;
}
/// <summary>Argument manquant, option inconnue : l'appel est incorrect (code de sortie 1).</summary>
internal sealed class ErreurUsageException : Exception
{
public ErreurUsageException(string message)
: base(message)
{
}
}
/// <summary><c>--aide</c> demandé : ce n'est pas une erreur (code de sortie 0).</summary>
internal sealed class AideDemandeeException : Exception
{
}