feat: add PuppetConfig and integrate with CLI

Introduced a new PuppetConfig class in the Puppeteer
namespace to manage Puppeteer configurations. Updated
the CLI project to reference the Puppeteer project and
added a new method in DownloadBuilder for using a
Puppet manipulator.

This change enables better configuration management
for Puppeteer within the CLI.
```
This commit is contained in:
qwsdcvghyu89
2025-06-25 22:09:59 +03:00
parent a5cc48a0d3
commit 487fdcc77b
5 changed files with 232 additions and 20 deletions
+10
View File
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Beam.Puppeteer {
internal class PuppetConfig {
}
}
@@ -23,6 +23,9 @@
<ProjectReference Include="..\Beam.Exports\Beam.Exports.csproj"> <ProjectReference Include="..\Beam.Exports\Beam.Exports.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Beam.Puppeteer\Beam.Puppeteer.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<ProjectReference Include="..\Beam\Beam.csproj"> <ProjectReference Include="..\Beam\Beam.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
+1
View File
@@ -55,6 +55,7 @@ namespace Beam.Temporary.Cli {
IContextStage WithRetryReporter(IProgress<RetryReport> reporter); IContextStage WithRetryReporter(IProgress<RetryReport> reporter);
DownloadEnumerable<OutType> Build(); DownloadEnumerable<OutType> Build();
IContextStage UseFragments(); IContextStage UseFragments();
IContextStage UsePuppet(AsyncManipulator manipulator);
} }
/* ────────────────────────── Implementation ────────────────────────── */ /* ────────────────────────── Implementation ────────────────────────── */
+216 -18
View File
@@ -5,116 +5,290 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Beam { namespace Beam {
/// <summary>
/// Describes where a <see cref="Parameter"/> token should be inserted relative to the runtime value
/// that ultimately replaces it when building a <see cref="SourceLink"/>.
/// </summary>
[Flags] [Flags]
public enum Position { public enum Position {
Before = 0b01, /// <summary>
After = 0b10, /// The parameter name is written <em>before</em> its runtime value
BeforeAndAfter = 0b11 /// (e.g. <c>id42</c> when the name is <c>id</c> and the value is <c>42</c>).
/// </summary>
Before = 0b01,
/// <summary>
/// The parameter name is written <em>after</em> its runtime value
/// (e.g. <c>42id</c>).
/// </summary>
After = 0b10,
/// <summary>
/// The parameter name is written both before and after the value
/// (e.g. <c>id42id</c>).
/// </summary>
BeforeAndAfter = 0b11
} }
/// <summary>
/// Represents a single placeholder that will be substituted when a link is built.
/// </summary>
/// <param name="name">Identifier that appears in the final link according to <paramref name="position"/>.</param>
/// <param name="position">Controls whether <paramref name="name"/> is written before, after, or on both sides of the value.</param>
public class Parameter(string name, Position position = Position.Before) { public class Parameter(string name, Position position = Position.Before) {
/// <summary>
/// Gets or sets the identifier that frames the value when the link is rendered.
/// </summary>
public string Name { get; set; } = name; public string Name { get; set; } = name;
/// <summary>
/// Gets or sets the position at which <see cref="Name"/> is emitted relative to the runtime value.
/// </summary>
public Position Position { get; set; } = position; public Position Position { get; set; } = position;
/// <summary>
/// Creates a shallow copy whose mutable members are detached from the original instance.
/// </summary>
/// <returns>A new <see cref="Parameter"/> with identical <see cref="Name"/> and <see cref="Position"/>.</returns>
public Parameter Clone()
=> new(Name, Position);
} }
/// <summary>
/// Describes one path segment of a URL and the collection of <see cref="Parameter"/> tokens that belong to it.
/// </summary>
/// <param name="name">Literal part of the segment that precedes the parameters (may be empty).</param>
/// <param name="separator">Optional string placed <em>between</em> adjacent parameters when more than one is present.</param>
/// <param name="suffix">Optional string appended <em>after</em> the last parameter in the segment.</param>
public class LinkSegment(string name, string separator = "", string suffix = "") { public class LinkSegment(string name, string separator = "", string suffix = "") {
/// <summary>
/// Gets or sets the literal path component for this segment.
/// </summary>
public string Name { get; set; } = name; public string Name { get; set; } = name;
/// <summary>
/// Gets or sets the collection of parameters that appear within the segment.
/// </summary>
public List<Parameter> Parameters { get; set; } = []; public List<Parameter> Parameters { get; set; } = [];
/// <summary>
/// Gets or sets the string inserted between parameters when the segment is rendered.
/// </summary>
public string Separator { get; set; } = separator; public string Separator { get; set; } = separator;
/// <summary>
/// Gets or sets the string appended after the last parameter when the segment is rendered.
/// </summary>
public string Suffix { get; set; } = suffix; public string Suffix { get; set; } = suffix;
/// <summary>
/// Produces a deep copy whose <see cref="Parameters"/> list contains cloned <see cref="Parameter"/> objects.
/// </summary>
public LinkSegment Clone()
=> new LinkSegment(Name, Separator, Suffix) {
Parameters = [.. Parameters.Select(static x => x.Clone())]
};
/// <summary>
/// Replaces <see cref="Parameters"/> with a new set whose items all use <see cref="Position.Before"/>.
/// </summary>
/// <param name="parameters">Parameter identifiers to add.</param>
/// <returns>This instance for fluent calls.</returns>
public LinkSegment WithParameters(params string[] parameters) { public LinkSegment WithParameters(params string[] parameters) {
Parameters = parameters.Select((x) => new Parameter(x)).ToList(); Parameters = parameters.Select(static x => new Parameter(x)).ToList();
return this; return this;
} }
/// <summary>
/// Replaces <see cref="Parameters"/> with a new set using explicit name/position tuples.
/// </summary>
/// <param name="parameters">Tuples of parameter identifier and desired position.</param>
/// <returns>This instance for fluent calls.</returns>
public LinkSegment WithParameters(params (string, Position)[] parameters) { public LinkSegment WithParameters(params (string, Position)[] parameters) {
Parameters = parameters.Select((x) => new Parameter(x.Item1, x.Item2)).ToList(); Parameters = parameters.Select(static x => new Parameter(x.Item1, x.Item2)).ToList();
return this; return this;
} }
} }
/// <summary>
/// Fluent helper for composing stronglytyped, templatedriven source links.
/// </summary>
/// <remarks>
/// The builder captures a static template (protocol, host, path segments and their parameters) and is later supplied with
/// a flat array of values that populate all parameters in lefttoright order.
/// </remarks>
/// <param name="host">DNS host name (e.g. <c>api.example.com</c>).</param>
/// <param name="protocol">Transport protocol; defaults to <c>https</c>.</param>
public class SourceLinkBuilder(string host, string protocol = "https") { public class SourceLinkBuilder(string host, string protocol = "https") {
/// <summary>
/// Gets or sets the scheme part of the URL (e.g. <c>https</c>, <c>http</c>).
/// </summary>
public string Protocol { get; set; } = protocol; public string Protocol { get; set; } = protocol;
/// <summary>
/// Gets or sets the host portion of the URL.
/// </summary>
public string Host { get; set; } = host; public string Host { get; set; } = host;
/// <summary>
/// Gets or sets the ordered collection of path segments.
/// </summary>
public List<LinkSegment> Segments { get; set; } = []; public List<LinkSegment> Segments { get; set; } = [];
/// <summary>
/// Produces a deep copy whose <see cref="Segments"/> and contained collections are detached from the original.
/// </summary>
public SourceLinkBuilder Clone()
=> new SourceLinkBuilder(Protocol, Host) {
Segments = [.. Segments.Select(static x => x.Clone())]
};
#region Helpers suffix & separator
/// <summary>
/// Returns the suffix of the <paramref name="segmentIndex"/>th segment.
/// </summary>
/// <param name="segmentIndex">Zerobased segment index.</param>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="segmentIndex"/> is outside the valid range.</exception>
public string GetSuffix(int segmentIndex) { public string GetSuffix(int segmentIndex) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
return Segments[segmentIndex].Suffix; return Segments[segmentIndex].Suffix;
} }
/// <summary>
/// Returns the suffix of the last segment.
/// </summary>
public string GetSuffix() public string GetSuffix()
=> GetSuffix(Segments.Count - 1); => GetSuffix(Segments.Count - 1);
/// <summary>
/// Returns the separator used by the <paramref name="segmentIndex"/>th segment.
/// </summary>
/// <param name="segmentIndex">Zerobased segment index.</param>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="segmentIndex"/> is outside the valid range.</exception>
public string GetSeparator(int segmentIndex) { public string GetSeparator(int segmentIndex) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
return Segments[segmentIndex].Separator; return Segments[segmentIndex].Separator;
} }
/// <summary>
/// Returns the separator of the last segment.
/// </summary>
public string GetSeparator() public string GetSeparator()
=> GetSeparator(Segments.Count - 1); => GetSeparator(Segments.Count - 1);
/// <summary>
/// Assigns a new suffix to the <paramref name="segmentIndex"/>th segment.
/// </summary>
/// <param name="segmentIndex">Zerobased segment index.</param>
/// <param name="suffix">String appended after the segment's parameters.</param>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="segmentIndex"/> is outside the valid range.</exception>
public void SetSuffix(int segmentIndex, string suffix) { public void SetSuffix(int segmentIndex, string suffix) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
var seg = Segments[segmentIndex]; Segments[segmentIndex].Suffix = suffix;
seg.Suffix = suffix;
} }
/// <summary>
/// Assigns a new suffix to the last segment.
/// </summary>
public void SetSuffix(string suffix) public void SetSuffix(string suffix)
=> SetSuffix(Segments.Count - 1, suffix); => SetSuffix(Segments.Count - 1, suffix);
/// <summary>
/// Assigns a new separator to the <paramref name="segmentIndex"/>th segment.
/// </summary>
/// <param name="segmentIndex">Zerobased segment index.</param>
/// <param name="separator">String inserted between parameters belonging to the same segment.</param>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="segmentIndex"/> is outside the valid range.</exception>
public void SetSeparator(int segmentIndex, string separator) { public void SetSeparator(int segmentIndex, string separator) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
var seg = Segments[segmentIndex]; Segments[segmentIndex].Separator = separator;
seg.Separator = separator;
} }
/// <summary>
/// Assigns a new separator to the last segment.
/// </summary>
public void SetSeparator(string separator) public void SetSeparator(string separator)
=> SetSeparator(Segments.Count - 1, separator); => SetSeparator(Segments.Count - 1, separator);
#endregion
#region Segment manipulation
/// <summary>
/// Appends a new segment.
/// </summary>
/// <param name="name">Literal portion of the segment.</param>
/// <param name="separator">Optional separator for subsequent parameters; <see langword="null"/> keeps the current default.</param>
/// <exception cref="ArgumentException">If <paramref name="name"/> is <see langword="null"/>, empty, or whitespace.</exception>
public void AddSegment(string name, string? separator = null) { public void AddSegment(string name, string? separator = null) {
ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentException.ThrowIfNullOrWhiteSpace(name);
Segments.Add(new LinkSegment(name, separator)); Segments.Add(new LinkSegment(name, separator));
} }
/// <summary>
/// Replaces the whole <see cref="Segments"/> collection with the supplied <paramref name="segments"/>, each represented as a <see cref="LinkSegment"/>.
/// </summary>
/// <returns>This instance for fluent calls.</returns>
public SourceLinkBuilder WithSegments(params IEnumerable<string> segments) { public SourceLinkBuilder WithSegments(params IEnumerable<string> segments) {
Segments = segments.Select((x) => new LinkSegment(x)).ToList(); Segments = segments.Select(static x => new LinkSegment(x)).ToList();
return this; return this;
} }
/// <summary>
/// Replaces the <see cref="Segments"/> collection with <paramref name="count"/> empty segments.
/// </summary>
/// <param name="count">Number of segments to create.</param>
public SourceLinkBuilder WithSegments(int count) public SourceLinkBuilder WithSegments(int count)
=> WithSegments(Enumerable.Repeat("", count)); => WithSegments(Enumerable.Repeat("", count));
#endregion
#region Parameter manipulation (fluent)
/// <summary>
/// Replaces parameters of the <paramref name="i"/>th segment using the supplied identifiers.
/// </summary>
public SourceLinkBuilder WithParameters(int i, params string[] parameters) { public SourceLinkBuilder WithParameters(int i, params string[] parameters) {
Segments[i] Segments[i].WithParameters(parameters);
.WithParameters(parameters);
return this; return this;
} }
/// <summary>
/// Replaces parameters of the <paramref name="i"/>th segment using explicit name/position tuples.
/// </summary>
public SourceLinkBuilder WithParameters(int i, params (string, Position)[] parameters) { public SourceLinkBuilder WithParameters(int i, params (string, Position)[] parameters) {
Segments[i] Segments[i].WithParameters(parameters);
.WithParameters(parameters);
return this; return this;
} }
#endregion
#region Parameter manipulation (imperative)
/// <summary>
/// Adds new parameters to the specified segment without clearing existing ones.
/// </summary>
/// <param name="segmentIndex">Zerobased segment index.</param>
/// <param name="parameters">Identifiers of parameters to add.</param>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="segmentIndex"/> is invalid.</exception>
/// <exception cref="ArgumentException">If any parameter identifier is <see langword="null"/>, empty or whitespace.</exception>
public void AddParameters(int segmentIndex, params string[] parameters) { public void AddParameters(int segmentIndex, params string[] parameters) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
var seg = Segments[segmentIndex]; var seg = Segments[segmentIndex];
foreach(var parameter in parameters) { foreach (var parameter in parameters) {
ArgumentException.ThrowIfNullOrWhiteSpace(parameter); ArgumentException.ThrowIfNullOrWhiteSpace(parameter);
seg.Parameters.Add(new Parameter(parameter)); seg.Parameters.Add(new Parameter(parameter));
} }
} }
/// <summary>
/// Adds parameters to the last segment.
/// </summary>
public void AddParameters(params string[] parameters) public void AddParameters(params string[] parameters)
=> AddParameters(Segments.Count - 1, parameters); => AddParameters(Segments.Count - 1, parameters);
/// <summary>
/// Replaces the entire parameter list of the specified segment.
/// </summary>
public void SetParameters(int segmentIndex, params string[] parameters) { public void SetParameters(int segmentIndex, params string[] parameters) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(segmentIndex, Segments.Count);
ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex); ArgumentOutOfRangeException.ThrowIfNegative(segmentIndex);
@@ -123,21 +297,38 @@ namespace Beam {
AddParameters(segmentIndex, parameters); AddParameters(segmentIndex, parameters);
} }
/// <summary>
/// Replaces the parameter list of the last segment.
/// </summary>
public void SetParameters(params string[] parameters) public void SetParameters(params string[] parameters)
=> SetParameters(Segments.Count - 1, parameters); => SetParameters(Segments.Count - 1, parameters);
#endregion
/// <summary>
/// Returns the total number of <see cref="Parameter"/> tokens across all segments.
/// </summary>
public int GetParameterCount() { public int GetParameterCount() {
int count = 0; int count = 0;
foreach(var segment in Segments) { foreach (var segment in Segments) {
count += segment.Parameters.Count; count += segment.Parameters.Count;
} }
return count; return count;
} }
#region Build
/// <summary>
/// Produces a concrete <see cref="SourceLink"/> using values from an external <see cref="State"/> object.
/// </summary>
/// <param name="parameterValues">Object providing positional values.</param>
public SourceLink Build(State parameterValues) public SourceLink Build(State parameterValues)
=> Build(parameterValues.GetState()); => Build(parameterValues.GetState());
/// <summary>
/// Produces a concrete <see cref="SourceLink"/> by substituting <paramref name="parameterValues"/> into the template.
/// </summary>
/// <param name="parameterValues">Flat array of values that will be written in the order that parameters appear when segments are enumerated lefttoright.</param>
/// <returns>The completed <see cref="SourceLink"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">If the supplied value count does not match <see cref="GetParameterCount"/>().</exception>
public SourceLink Build(params object[] parameterValues) { public SourceLink Build(params object[] parameterValues) {
ArgumentOutOfRangeException.ThrowIfNotEqual(parameterValues.Length, GetParameterCount()); ArgumentOutOfRangeException.ThrowIfNotEqual(parameterValues.Length, GetParameterCount());
@@ -145,22 +336,29 @@ namespace Beam {
link.Append(Protocol); link.Append(Protocol);
link.Append("://"); link.Append("://");
link.Append(Host); link.Append(Host);
int pvC = 0; int pvC = 0;
foreach(var segment in Segments) { foreach (var segment in Segments) {
link.Append('/'); link.Append('/');
link.Append(segment.Name); link.Append(segment.Name);
for (int i = 0; i < segment.Parameters.Count; i++) { for (int i = 0; i < segment.Parameters.Count; i++) {
if (segment.Parameters[i].Position.HasFlag(Position.Before)) if (segment.Parameters[i].Position.HasFlag(Position.Before))
link.Append(segment.Parameters[i].Name); link.Append(segment.Parameters[i].Name);
link.Append(parameterValues[pvC++]); link.Append(parameterValues[pvC++]);
if (segment.Parameters[i].Position.HasFlag(Position.After)) if (segment.Parameters[i].Position.HasFlag(Position.After))
link.Append(segment.Parameters[i].Name); link.Append(segment.Parameters[i].Name);
if (i + 1 < segment.Parameters.Count && segment.Separator is not null) if (i + 1 < segment.Parameters.Count && segment.Separator is not null)
link.Append(segment.Separator); link.Append(segment.Separator);
} }
link.Append(segment.Suffix);
} }
return new SourceLink(link.ToString()); return new SourceLink(link.ToString());
} }
#endregion
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@
<Title>Beam</Title> <Title>Beam</Title>
<Authors>aeqw89</Authors> <Authors>aeqw89</Authors>
<Company>qwsdcvghyu</Company> <Company>qwsdcvghyu</Company>
<Version>1.3.0</Version> <Version>1.3.2</Version>
<Description>A library for downloading internet resources</Description> <Description>A library for downloading internet resources</Description>
<PackageProjectUrl>https://github.com/qwsdcvghyu89/Beam</PackageProjectUrl> <PackageProjectUrl>https://github.com/qwsdcvghyu89/Beam</PackageProjectUrl>
<RepositoryUrl>https://github.com/qwsdcvghyu89/Beam</RepositoryUrl> <RepositoryUrl>https://github.com/qwsdcvghyu89/Beam</RepositoryUrl>