Skip to content

Commit 77731e5

Browse files
committed
Bunch of new generators, Internet module
- Added domain generator - Added email generator - Moved username generator to Internet module from Person - Added range and replace utils generators for strings - Added a locale parameter to the generated faker - Added a whole slew of words to the lorem ipsum generator - Moved lorem ipsum corpus to a locale file - Added options to the lorem ipsum generator. So far, they allow the user to specify how many words at the beginning of the generated text should follow the classic "lorem ipsum"
1 parent ca9bc1e commit 77731e5

22 files changed

Lines changed: 1689 additions & 92 deletions

Forged.Core/Forge.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ public sealed class Forge
4242
public ForgeBasic Basic { get; }
4343

4444
/// <summary>
45-
/// Gets the module for generating person-related data.
45+
/// Gets the module for generating internet data.
4646
/// </summary>
47-
public ForgePerson Person { get; }
47+
public ForgeInternet Internet { get; }
4848

4949
/// <summary>
5050
/// Initializes a new instance of the <see cref="Forge"/> class.
@@ -60,6 +60,6 @@ public Forge(Random? random, CultureInfo? locale)
6060
Temporal = new ForgeTemporal(this);
6161
Text = new ForgeText(this);
6262
Basic = new ForgeBasic(this);
63-
Person = new ForgePerson(this);
63+
Internet = new ForgeInternet(this);
6464
}
6565
}

Forged.Core/Generators/Extensions/StringGeneratorExtensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Globalization;
2+
using System.Text.RegularExpressions;
23
using Forged.Core.Generators.Utility.Text;
34

45
namespace Forged.Core.Generators.Extensions;
@@ -26,6 +27,45 @@ public Generator<string> ToUpper()
2627
/// <returns>A generator that produces lowercase strings.</returns>
2728
public Generator<string> ToLower()
2829
=> new LowercaseGenerator(generator, generator.Forge);
30+
31+
/// <summary>
32+
/// Creates a generator that produces substrings of a specified range of characters.
33+
/// </summary>
34+
/// <param name="start">The starting index of the range, inclusive.</param>
35+
/// <param name="end">
36+
/// The ending index of the range, exclusive. If null, the generator will include all characters up to the length of the string.
37+
/// </param>
38+
/// <returns>A generator that produces substrings within the specified range.</returns>
39+
public Generator<string> Range(int start, int? end = null)
40+
=> new RangeGenerator(generator, start, end, generator.Forge);
41+
42+
/// <summary>
43+
/// Creates a generator that replaces all occurrences of a specified string in the generated text with another string.
44+
/// </summary>
45+
/// <param name="oldValue">The string to be replaced in the generated text.</param>
46+
/// <param name="newValue">The string that will replace all occurrences of <paramref name="oldValue"/>.</param>
47+
/// <returns>A generator that produces strings with the specified replacements applied.</returns>
48+
public Generator<string> Replace(string oldValue, string newValue)
49+
=> new ReplaceGenerator(generator, oldValue, newValue, generator.Forge);
50+
51+
/// <summary>
52+
/// Creates a generator that replaces all occurrences of a specified char in the generated text with another char.
53+
/// </summary>
54+
/// <param name="oldValue">The char to be replaced in the generated text.</param>
55+
/// <param name="newValue">The char that will replace all occurrences of <paramref name="oldValue"/>.</param>
56+
/// <returns>A generator that produces strings with the specified replacements applied.</returns>
57+
public Generator<string> Replace(char oldValue, char newValue)
58+
=> new ReplaceGenerator(generator, oldValue.ToString(), newValue.ToString(), generator.Forge);
59+
60+
/// <summary>
61+
/// Creates a generator that replaces substrings in the generated strings
62+
/// based on a provided regular expression.
63+
/// </summary>
64+
/// <param name="regex">The regular expression used to find substrings to replace.</param>
65+
/// <param name="newValue">The value to replace matched substrings with.</param>
66+
/// <returns>A generator that produces strings with substrings replaced according to the specified regular expression and replacement value.</returns>
67+
public Generator<string> Replace(Regex regex, string newValue)
68+
=> new RegexReplaceGenerator(generator, regex, newValue, generator.Forge);
2969

3070
/// <summary>
3171
/// Creates a generator that converts strings to title case.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using JetBrains.Annotations;
4+
5+
namespace Forged.Core.Generators.Internet;
6+
7+
/// <summary>
8+
/// Generates random domain.
9+
/// </summary>
10+
public sealed class DomainGenerator : Generator<string>
11+
{
12+
private readonly float _ccSldChance;
13+
private readonly Lazy<DomainData> _cache;
14+
15+
/// <summary>
16+
/// Generates random domain.
17+
/// </summary>
18+
/// <param name="ccSldChance">Chance to prepend the domain with a functional second-level domain</param>
19+
/// <param name="forge">The Forge instance to use.</param>
20+
public DomainGenerator(float ccSldChance, Forge forge) : base(forge)
21+
{
22+
_ccSldChance = ccSldChance;
23+
_cache = new(() => FileLoader.LoadData("en", "internet/domains", DomainDataContext.Default.DomainData));
24+
}
25+
26+
/// <summary>
27+
/// Generates a random domain.
28+
/// </summary>
29+
/// <returns>A random domain.</returns>
30+
public override string Generate()
31+
{
32+
var data = _cache.Value;
33+
34+
var domain = Rng.GetItems(data.Tld, 1)[0];
35+
if (_ccSldChance <= float.Epsilon || Rng.NextDouble() <= _ccSldChance)
36+
{
37+
return domain;
38+
}
39+
40+
var slds = data.CcSld.Except([domain]).ToArray();
41+
var sld = Rng.GetItems(slds, 1)[0];
42+
43+
return $"{sld}.{domain}";
44+
}
45+
}
46+
47+
internal sealed record DomainData(string[] Tld, string[] CcSld);
48+
49+
[UsedImplicitly]
50+
[JsonSerializable(typeof(DomainData))]
51+
[JsonSourceGenerationOptions(
52+
AllowTrailingCommas = true,
53+
ReadCommentHandling = JsonCommentHandling.Skip,
54+
PropertyNameCaseInsensitive = true
55+
)]
56+
internal sealed partial class DomainDataContext : JsonSerializerContext;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Collections.Concurrent;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using JetBrains.Annotations;
5+
using NetEscapades.EnumGenerators;
6+
7+
namespace Forged.Core.Generators.Internet;
8+
9+
/// <summary>
10+
/// Represents a generator for creating email addresses based on the specified kind and optional parameters.
11+
/// </summary>
12+
/// <param name="kind">The type of email address to generate.</param>
13+
/// <param name="nameGenerator">An optional generator to supply email address usernames. Defaults to null, which uses a username generator.</param>
14+
/// <param name="forge">The Forge instance to use.</param>
15+
/// <exception cref="ArgumentOutOfRangeException">Thrown if an invalid <see cref="EmailKind"/> is provided.</exception>
16+
public sealed class EmailGenerator(EmailKind kind, IGenerator<string>? nameGenerator, Forge forge) : Generator<string>(forge)
17+
{
18+
private static ConcurrentBag<string>? _providers;
19+
20+
public override string Generate()
21+
{
22+
_providers ??= new(FileLoader.LoadData(Locale.Name, "internet/email", EmailDataContext.Default.ListString));
23+
24+
var name = (nameGenerator ?? Forge.Internet.Username(leetChance: 0)).Generate();
25+
26+
var domain = kind switch
27+
{
28+
EmailKind.Known => Rng.GetItems(_providers.ToArray(), 1)[0],
29+
EmailKind.Example => Rng.GetItems(["example.com", "example.org", "example.net"], 1)[0],
30+
EmailKind.Random => $"{Forge.Text.Pronounceable(1, 3)}.{Forge.Internet.Domain()}",
31+
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null)
32+
};
33+
34+
return $"{name}@{domain}";
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Represents the type of email address to generate, dictating the format and domain selection process.
40+
/// </summary>
41+
/// <remarks>
42+
/// This enum is used to specify the strategy for generating email addresses. There are three possible kinds:
43+
/// - Known: Email addresses with domains from a predefined list of commonly used providers.
44+
/// - Example: Email addresses with domains explicitly designed for testing, such as "example.com".
45+
/// - Random: Email addresses with randomly generated domain names.
46+
/// </remarks>
47+
[EnumExtensions]
48+
public enum EmailKind
49+
{
50+
/// <summary>
51+
/// Email addresses with domains from a predefined list of commonly used providers.
52+
/// </summary>
53+
Known,
54+
/// <summary>
55+
/// Email addresses with domains explicitly designed for testing, such as "example.com".
56+
/// </summary>
57+
Example,
58+
/// <summary>
59+
/// Email addresses with randomly generated domain names.
60+
/// </summary>
61+
Random,
62+
}
63+
64+
[UsedImplicitly]
65+
[JsonSerializable(typeof(List<string>))]
66+
[JsonSourceGenerationOptions(
67+
AllowTrailingCommas = true,
68+
ReadCommentHandling = JsonCommentHandling.Skip,
69+
PropertyNameCaseInsensitive = true
70+
)]
71+
internal sealed partial class EmailDataContext : JsonSerializerContext;

Forged.Core/Generators/Person/UsernameGenerator.cs renamed to Forged.Core/Generators/Internet/UsernameGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System.Text.Json.Serialization;
44
using JetBrains.Annotations;
55

6-
namespace Forged.Core.Generators.Person;
6+
namespace Forged.Core.Generators.Internet;
77

88
/// <summary>
99
/// Generates random usernames with configurable options for prefixes, suffixes, and leet-speak substitutions.
@@ -50,7 +50,7 @@ public sealed class UsernameGenerator(float prefixChance, float suffixChance, fl
5050
/// <returns>A randomly generated username that may include a prefix, suffix, and leet-speak substitutions based on the configured probabilities.</returns>
5151
public override string Generate()
5252
{
53-
var data = FileLoader.LoadData(Locale.Name, "username", UserDataContext.Default.UserData);
53+
var data = FileLoader.LoadData(Locale.Name, "internet/username", UserDataContext.Default.UserData);
5454

5555
var usePrefix = Rng.NextSingle() < prefixChance;
5656
var useSuffix = Rng.NextSingle() < suffixChance;
Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
using System.Collections.Concurrent;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using JetBrains.Annotations;
5+
16
namespace Forged.Core.Generators.Text;
27

38
/// <summary>
@@ -6,48 +11,42 @@ namespace Forged.Core.Generators.Text;
611
/// <param name="minWords">The minimum number of words to generate.</param>
712
/// <param name="maxWords">The maximum number of words to generate.</param>
813
/// <param name="forge">The Forge instance to use.</param>
9-
public sealed class LoremIpsumGenerator(int minWords, int maxWords, Forge forge) : Generator<string>(forge)
14+
public sealed class LoremIpsumGenerator(int minWords, int maxWords, LoremIpsumGenerator.Options? options, Forge forge) : Generator<string>(forge)
1015
{
11-
private static readonly string[] Lipsum =
12-
[
13-
// repeat common words for higher chance
14-
"lorem", "ipsum", "dolor", "sit", "amet",
15-
"lorem", "ipsum", "dolor",
16-
17-
// base lipsum corpus
18-
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
19-
"sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
20-
"magna", "aliqua", "enim", "ad", "minim", "veniam", "quis", "nostrud",
21-
"exercitation", "ullamco", "laboris", "nisi", "aliquip", "ex", "ea", "commodo",
22-
"consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate",
23-
"velit", "esse", "cillum", "fugiat", "nulla", "pariatur", "excepteur",
24-
"sint", "occaecat", "cupidatat", "non", "proident", "sunt", "culpa",
25-
"qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum",
26-
27-
// extended Latin-like filler vocabulary
28-
"praesent", "venenatis", "lectus", "aenean", "scelerisque", "phasellus",
29-
"viverra", "turpis", "massa", "ultricies", "faucibus", "pulvinar",
30-
"elementum", "nunc", "lobortis", "facilisis", "iaculis", "gravida",
31-
"porttitor", "congue", "placerat", "hendrerit", "integer", "neque",
32-
"vehicula", "sagittis", "suscipit", "malesuada", "ornare", "condimentum",
33-
"tincidunt", "augue", "risus", "bibendum", "phasellus", "euismod",
34-
"interdum", "fermentum", "dictum", "nibh", "blandit", "curae",
35-
"lacinia", "mauris", "accumsan", "imperdiet", "phasellus", "dapibus",
36-
"eros", "cras", "sollicitudin", "habitasse", "platea", "dictumst",
37-
"maecenas", "rutrum", "varius", "quam", "vitae", "sapien", "tellus",
38-
"mauris", "pretium", "fusce", "ornare", "donec", "ullamcorper"
39-
];
40-
16+
private static readonly string[] Lorem = ["lorem", "ipsum", "dolor", "sit", "amet"];
17+
18+
private static ConcurrentBag<string>? _cache;
19+
4120
/// <summary>
4221
/// Generates a random Lorem Ipsum string.
4322
/// </summary>
4423
/// <returns>A random Lorem Ipsum string with the specified number of words.</returns>
4524
public override string Generate()
4625
{
26+
_cache ??= new(FileLoader.LoadData("en", "text/lorem", LoremContext.Default.ListString));
27+
4728
var length = minWords == maxWords
4829
? minWords
4930
: Rng.Next(minWords, maxWords + 1);
5031

51-
return string.Join(" ", Rng.GetItems(Lipsum, length));
32+
if (options is not { Starter: > 0})
33+
{
34+
return string.Join(' ', Rng.GetItems(_cache.ToArray(), length));
35+
}
36+
37+
string[] words = [
38+
..Lorem[..options.Starter],
39+
..Rng.GetItems(_cache.ToArray(), length)
40+
];
41+
42+
return string.Join(' ', words);
5243
}
44+
45+
[UsedImplicitly]
46+
public sealed record Options(int Starter = 0);
5347
}
48+
49+
[UsedImplicitly]
50+
[JsonSerializable(typeof(List<string>))]
51+
[JsonSourceGenerationOptions(AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip)]
52+
internal sealed partial class LoremContext : JsonSerializerContext;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace Forged.Core.Generators.Utility.Text;
2+
3+
/// <summary>
4+
/// Represents a generator that produces a substring of a specified range from a generated string value.
5+
/// </summary>
6+
/// <typeparam name="T">The type of the generated value, constrained to strings in this implementation. </typeparam>
7+
/// <remarks>
8+
/// The <c>RangeGenerator</c> class is a specialized generator for creating substrings from
9+
/// the output of an inner <c>Generator&lt;string&gt;</c>. It takes a range specified by a starting index
10+
/// and an optional ending index.
11+
/// </remarks>
12+
/// <exception cref="ArgumentOutOfRangeException">
13+
/// Thrown if the specified range is outside the bounds of the string generated by the inner generator.
14+
/// </exception>
15+
/// <param name="innerGenerator">
16+
/// The inner string generator whose output will be used to extract the range.
17+
/// </param>
18+
/// <param name="start">
19+
/// The starting index of the substring within the string generated by the inner generator.
20+
/// </param>
21+
/// <param name="end">
22+
/// The optional ending index of the substring within the generated string. If not supplied, the substring
23+
/// will include characters up to the end of the inner-generator's output.
24+
/// </param>
25+
/// <param name="forge">
26+
/// The <c>Forge</c> instance providing shared dependencies such as a random number generator
27+
/// and locale information.
28+
/// </param>
29+
public sealed class RangeGenerator(Generator<string> innerGenerator, int start, int? end, Forge forge) : Generator<string>(forge)
30+
{
31+
public override string Generate()
32+
{
33+
var str = innerGenerator.Generate().AsSpan();
34+
35+
var newEnd = end ?? str.Length;
36+
37+
if (start >= newEnd)
38+
{
39+
return string.Empty;
40+
}
41+
42+
if (start > str.Length || newEnd > str.Length)
43+
{
44+
throw new ArgumentOutOfRangeException();
45+
}
46+
47+
var s = start < 0 ? str.Length - start : start;
48+
var e = newEnd < 0 ? str.Length - newEnd : newEnd;
49+
50+
return str[s..e].ToString();
51+
}
52+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Text.RegularExpressions;
2+
3+
namespace Forged.Core.Generators.Utility.Text;
4+
5+
/// <summary>
6+
/// A generator that transforms the output of another string generator by replacing all occurrences
7+
/// of a specified substring with a given replacement string.
8+
/// </summary>
9+
public sealed class RegexReplaceGenerator(Generator<string> innerGenerator, Regex regex, string to, Forge forge) : Generator<string>(forge)
10+
{
11+
public override string Generate()
12+
{
13+
var str = innerGenerator.Generate();
14+
return regex.Replace(str, to);
15+
}
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Forged.Core.Generators.Utility.Text;
2+
3+
/// <summary>
4+
/// A generator that transforms the output of another string generator by replacing all occurrences
5+
/// of a specified substring with a given replacement string.
6+
/// </summary>
7+
public sealed class ReplaceGenerator(Generator<string> innerGenerator, string from, string to, Forge forge) : Generator<string>(forge)
8+
{
9+
public override string Generate()
10+
{
11+
var str = innerGenerator.Generate();
12+
return str.Replace(from, to);
13+
}
14+
}

0 commit comments

Comments
 (0)